El proyecto gvSIG se presenta como un framework sobre el que se pueden ir añadiendo plugins que le doten de nuevas funcionalidades. Esta documentación le irá guiando sobre cual es la forma en que puede crear sus propios plugins para añadir a gvSIG.
La plataforma que proporciona gvSIG se sustenta en una arquitectura abierta en la que cada equipo que está desarrollando un plugin se puede centrar en su área de experiencia. Puede haber equipos especializados en librerías base, mientras que otros equipos se centran en desarrollar interfaces de usuario para que las herramientas de base estén accesibles para los usuarios finales.
gvSIG usa un modelo que consiste en presentar un conjunto de herramientas de forma homogénea desde el punto de vista del usuario. Las herramientas que se desarrollan se integran dentro del marco de gvSIG usando unos mecanismos ya definidos llamados plugins.
La plataforma gvSIG en sí misma esta construida a modo de capas, cada una de las cuales define sus propios puntos de extensión. A su vez, cada plugin puede definir sus propios puntos de extensión. Este modelo de plugins, permite a los desarrolladores añadir gran variedad de funcionalidades a la plataforma base de gvSIG, de forma que los artefactos de cada herramienta, como pueden ser los distintos tipos de capas, o botones, se presentan al usuario desde la plataforma común.
Los desarrolladores de plugins también se benefician de esta arquitectura. El framework base de gvSIG les proporciona una serie de servicios de los cuales ellos no tienen que preocuparse, pudiéndose centrar en las tareas especificas de su extensión.
gvSIG está estructurada en una serie de subsistemas los cuales están implementados como librerías y como plugins en si mismos.
La plataforma de gvSIG esta conformada en su núcleo por tres subsistemas:
- gvSIG. Representa los datos geográficos manejados por Fmap. En este subsistema encontraremos las clases que implementan la mayor parte de cuadros de diálogo que utiliza la aplicación final, así como las clases de soporte a esos cuadros de diálogo. Por ejemplo, aquí se encuentran los formularios para asignar leyendas, crear mapas y vistas, definir escalas, etc.
- FMap. Es el corazón SIG de la plataforma. Incluye todas las clases necesarias para manejar objetos SIG, así como drivers y adaptadores para manejar los formatos más usados para el almacenamiento de los datos cartográficos. Dentro de esta librería encontramos clases para leer y escribir los formatos soportados, dibujar los mapas a las escalas adecuadas, asignar leyendas, definir simbologías, realizar búsquedas, consultas, análisis, etc.
- Subdriver. En este subsistema encontramos todas las clases que permiten el acceso y la gestión de los datos.
Representa la parte visual de la aplicación. Es la representación gráfica de los datos geográficos. Gestiona el interface de usuario.
Se compone de un gestor gráfico llamado Andami y de los distintos documentos que pueden formar un proyecto.
- Andami: Proporciona los métodos necesarios para que los plugins puedan comunicarse tanto con la aplicación principal como entre ellos. Soporta también los métodos necesarios para gestionar el interface gráfico.
- Project: Es el contenedor de la estructura de documentos que hay cargados en un momento dado. Contiene también los datos básicos del proyecto (nombre, fecha,...)
- Documents: Son los distintos tipos de documentos que soporta la aplicación, en la actualidad existen 3 tipos de documentos, Vistas, Tablas y Mapas. Proporciona los puntos de extensión necesarios para incluir nuevos tipos de documentos.
- View: Representación gráfica de la cartografía así como la leyenda en el ToC.
- Layout: Es la representación gráfica de una vista en un soporte apto para imprimir.
- Table: Es la representación gráfica de los datos alfanuméricos.
- Layers: Son el conjunto de capas que pueden insertarse en una vista de gvSIG.
Con el paquete FMap se proporciona un control de interfaz de usuario junto con una serie de herramientas diseñadas para este control.
Es el controlador de la cartografía tanto para dibujarse como para acceder a los datos. Es el motor de SIG de la aplicación.
A grandes rasgos puede decirse que se compone de un gestor de herramientas, capas y orígenes de datos para las capas y las geometrías que se utilizarán para representar los datos en las capas.
- MapControl: Se encarga de dibujar y mantener la herramienta actual, conoce todas las herramientas que existen en la aplicación
- MapContext: Es el contexto de la parte gráfica. Contiene los elementos necesarios para que el MapControl pueda realizar su labor.
- Behaivor: Es un comportamiento de una herramienta. También dice como se comporta la herramienta gráficamente. Controla el dibujado de la herramienta y el iniciador de los eventos de la herramienta.
- Listeners: Son los encargados de gestionar los eventos de las distintas herramientas, ya sea propagándolos hacia quien corresponda o ejecutando las instrucciones necesarias.
- Layer: Contiene las características de la capa y las herramientas necesarias para su gestión.
- Geometrías: Son los distintos tipos de elementos gráficos que pueden ser representados dentro de una layer.
- DataSources y drivers: Contiene los métodos necesarios para la gestión de los datos tanto gráficos como alfanumericos.
Es el bloque que está recuperando los datos directamente de la fuente. Sirve de puente entre la aplicación y los datos. Contiene las clases necesarias para acceder a los datos, escribir datos en una fuente, así como las propiedades de acceso a fuentes remotas.
RemoteServices: Contiene las herramientas necesarias para unificar el acceso a datos remotos.
- Drivers: Gestionan los distintos tipos de datos soportados por gvSIG.
- DriverManager: Proporciona la carga y el acceso a los drivers disponibles en la aplicación tanto alfanuméricos como espaciales.
- WriterManager: Proporciona la carga y el acceso a los writers disponibles en la aplicación tanto alfanuméricos como espaciales.
- Writers: Permiten las operaciones de escritura sobre los distintos tipos de formatos soportados.
- VectorialSources: Proporciona acceso a los datos con las geometrías.
- DataSources: Proporciona acceso a los datos alfanuméricos.
- RasterSources: Proporciona acceso a los datos de tipo ráster.
Un framework es una estructura de soporte, que permite desarrollar una aplicación sobre él. En general, define la arquitectura básica de la aplicación y provee de servicios que agilizarán y simplificarán el desarrollo del proyecto.
Andami es el framework sobre el que se construye gvSIG. Está diseñado para ser extensible mediante plugins (módulos que añaden nueva funcionalidad a la aplicación). Además de proveer el mecanismo de plugins, Andami da soporte a gvSIG en la composición básica de la interfaz de usuario y en la gestión de ventanas y eventos. Proporciona una simplificación importante en la gestión de estos aspectos, de forma que resulta muy sencillo crear un plugin funcional de gvSIG, incluso para un recién llegado.
Dentro del código fuente de gvSIG, el proyecto Andami se localiza en el directorio "_fwAndami". Una vez compilado, se genera un fichero andami.jar que se suele situar en la raíz de la estructura de directorios de gvSIG.
Andami contiene la parte de gvSIG que inicia la ejecución, realiza la carga de plugins e inicializa todos los subsistemas de la aplicación. Principalmente, hay dos tipos de entidades que se relacionan con Andami, de forma directa o indirecta: plugins y resto de librerías. Un plugin es una librería (más algunos ficheros de configuración y datos) con una estructura especial que Andami reconoce y carga al arrancar, y que se conecta con la interfaz de usuario en unos puntos definidos por el plugin. El resto de librerías son aquellas que también usamos en gvSIG pero que no adoptan forma de plugins.
La mayor parte de la funcionalidad de gvSIG la proporcionan los plugins, que a su vez se apoyan en librerías. El propio gvSIG es un plugin de Andami, aportándole el concepto de documento (vista, tabla, mapa), la capacidad de cargar y salvar proyectos, el gestor de proyectos, y muchas otras funcionalidades. En las explicaciones, conviene por tanto distinguir cuándo nos referimos al plugin gvSIG (llamado com.iver.cit.gvsig), y cuándo a la aplicación gvSIG (que incluye Andami + plugin gvSIG + resto de plugins + librerías).
Las librerías que están en el directorio "bin/lib" de una instalación de gvSIG, se incluyen en el CLASSPATH inicial y por tanto están disponibles para toda la aplicación. Las librerías que están en el directorio de librerías de un plugin sólo están disponibles para el plugin que las contiene, y para los plugins que declaran una dependencia sobre aquél. Normalmente, todos los plugins definen una dependencia sobre el plugin gvSIG (com.iver.cit.gvsig), y por tanto las librerías de gvSIG están disponibles para todos ellos.
Andami proporciona una variedad de servicios a los plugins. Posteriormente, los explicaremos en profundidad, de momento vamos a enumerarlos brevemente para tener una idea de la funcionalidad aportada:
- Extensibilidad mediante plugins. Carga de plugins, carga dinámica de clases y recursos de los plugins, etc.
- Creación de la interfaz inicial de usuario desde ficheros XML (menús, barras de herramientas y barra de estado).
- Gestión de ventanas: creación, cerrado, cambio de propiedades, etc.
- Persistencia de datos: permite a los plugins almacenar datos en disco y recuperarlos posteriormente de forma muy sencilla.
- Traducciones: proporciona servicios de traducción de la interfaz de usuario.
- Ejecución de tareas en segundo plano: facilita la creación y gestión de tareas en segundo plano.
- Servicio de registro: aporta un registro (log) de sistema en el que anotar errores o eventos significativos (útil para detección y depuración de errores).
- Acceso al portapapeles.
- Creación de ficheros temporales.
- Acceso a recursos de los plugins
Vamos a mostrar una perspectiva general de los bloques funcionales de Andami, explicando la funcionalidad aportada por cada uno.
Entre las funciones aportadas por Andami se encuentra la gestión de ventanas. Esta gestión es bastante particular, porque el programador sólo necesita crear un panel con el contenido de la ventana, y Andami creará la ventana real. También se encarga de registrar la ventana en el menú de ventanas, y de enviar a primer plano la ventana correspondiente cuando seleccionamos una entrada de ese menú. Asimismo, debemos dirigirnos a Andami cuando queramos cambiar las propiedades de una ventana (redimensionar, maximizar, etc), para obtener la ventana activa, la lista de ventanas, etc.
Podemos encontrar más información en el capítulo Las ventanas de Andami .
Otra funcionalidad aportada por Andami es la creación de la interfaz de usuario y la gestión de los eventos producidas en la misma. En concreto, al arrancar Andami creará la ventana principal y los menús, las barras de herramientas y la barra de estado (con sus controles correspondientes). Además, ofrecerá los mecanismos adecuados para modificar estos elementos durante la ejecución del programa.
La gestión de la interfaz está muy relacionada con el siguiente bloque funcional, la gestión de plugins y extensiones, porque la creación inicial de la interfaz se realiza en respuesta a lo especificado en los ficheros de configuración de los plugins. En sentido inverso, las acciones que el usuario produzca sobre la interfaz (pulsar un menú o un botón, por ejemplo) también tienen efecto en la gestión de extensiones ya que Andami obtendrá la extensión asociada al botón o menú y le notificará la acción realizada. A continuación la extensión realizará el procesamiento adecuado para dicha acción.
La gestión de ventanas también está relacionada con la gestión de la interfaz porque las herramientas seleccionables están asociadas a la ventana activa, y por tanto cada vez que cambiamos de ventana, cambia también la herramienta seleccionada.
Debido a la íntima relación de la gestión de interfaz gráfica y la gestión de plugins, ambos aspectos se explican en el capítulo Plugins y extensiones.
Andami está diseñado para ser extensible mediante plugins y extensiones. Simplificando, las extensiones son clases Java que añaden funcionalidad a nuestra aplicación, y los plugins son agrupaciones de extensiones que permiten a Andami reconocer y cargar las extensiones.
Al arrancar, Andami inspecciona los plugins instalados, realiza la carga e inicialización de las extensiones que contienen y carga los elementos de la interfaz de usuario especificados en los ficheros de configuración del plugin. Cada uno de estos elementos (botones, menús, controles) está asociado a una extensión concreta y la visibilidad de estos elementos vendrá determinada por dicha extensión. Además, cuando el usuario pulse sobre un elemento, se enviará una notificación a la extensión asociada.
En el capítulo Plugins y extensiones se puede encontrar una explicación en profundidad de estos temas.
El último bloque funcional engloba una diversos servicios que Andami es capaz de prestar a los Plugins. Algunos de estos servicios están ligados a cada plugin y otros funcionan de forma idéntica para todos ellos, como se explica a continuación:
- Servicio de traducciones: Los plugins poseen unos ficheros de traducciones que son leídos durante el inicio de la aplicación. Estos ficheros contienen claves de traducción, y la traducción asociada a cada clave, para cada idioma. De esta forma, los plugins pueden solicitar la traducción de una clave y Andami devolverá la traducción para el idioma que el usuario haya elegido (a través de la configuración del idioma). Usando este mecanismo se puede presentar al usuario una interfaz totalmente traducida a su idioma preferido.
- Servicio de registro (log): El servicio de registro permite mantener un histórico de sucesos de una sesión completa de gvSIG. Los sucesos pueden tener distinta categoría: Información, Avisos, Errores o Información para Depuración. Al registrar algún mensaje en el log, automáticamente se registra también el plugin que genera el mensaje.
- Persistencia de datos: Existe un fichero (plugin-persistence.xml) en el que los plugins pueden almacenar información (ejemplos: un plugin de WMS podría almacenar la lista de servidores recientes; un plugin de nomenclátor podría almacenar las últimas búsquedas realizadas). De esta forma, al volver a arrancar gvSIG, los plugins pueden recuperar la información guardada y presentar (por ejemplo) la lista de servidores usados en la última sesión. Cada plugin sólo tiene acceso (de forma directa) a la información que él mismo produce. Debemos aclarar que este fichero es adecuado para guardar pequeñas cantidades de información de texto, pero no es adecuado para guardar otros tipos de información como miniaturas de la cartografía, un listado de nombres de municipios, etc.
- Ficheros temporales: Andami ofrece facilidades para crear ficheros temporales, que son eliminados automáticamente al salir de gvSIG (siempre que gvSIG finalice correctamente). Por una parte, si suministramos un nombre y unos datos, Andami creará el fichero temporal con el contenido proporcionado. Por otra parte, podemos solicitar la ruta del directorio de ficheros temporales y crear en él los ficheros que necesitemos. En ambos casos, los ficheros se eliminan automáticamente al salir de gvSIG.
- Portapapeles: Andami permite obtener texto del portapapeles, o depositar texto en él. Para otros tipos de datos, debe usarse directamente los métodos que Java ofrece para acceder al portapapeles.
- Tareas en segundo plano: Andami facilita la creación de tareas en segundo plano, permitiendo además que estas tareas sean cancelables (interrumpibles). Si deseamos realizar un procesamiento prolongado, conviene lanzarlo en un hilo separado (proceso en segundo plano), para no bloquear la interfaz gráfica. Un típico ejemplo de tarea a lanzar en segundo plano es la exportación de una capa a un fichero. Se presentará un diálogo al usuario con el progreso de la tarea, y un botón que permita la cancelación del proceso.
- Accesso a recursos del plugin: Andami posibilita acceder a recursos de los plugins (imágenes, ficheros XML, etc) usando rutas relativas al directorio raíz del plugin, para favorecer la portabilidad del código.
Se puede encontrar más detalles en el capítulo Servicios a los plugins.
Plugins y extensiones
En un gvSIG instalado, existe un subdirectorio llamado gvSIG/extensiones. Dicho subdirectorio contiene a su vez un conjunto de directorios, cada uno de los cuales constituye un plugin. Los plugins no son más que un conjunto de clases Java que añaden nuevas funcionalidades a la aplicación. Si inspeccionamos el mencionado directorio observaremos que existe un plugin llamado com.iver.core, que contiene libCorePlugin (el skin de Andami) y un plugin llamado com.iver.cit.gvsig, que contiene el plugin gvSIG (el plugin principal de la aplicación). Dependiendo de la instalación de gvSIG que hayamos realizado, podremos localizar otros plugins como com.iver.cit.gvsig.cad (funcionalidades de edición de cartografía), org.gvsig.scripting (que añade las capacidades de scripting), com.iver.cit.gvsig.wms (cliente de WMS), etc
Las nuevas funcionalidades añadidas por los plugins, deben conectarse de alguna forma con el resto de la aplicación. Este punto de conexión lo aportan las extensiones, que son clases Java que implementan la interfaz IExtension, y que hacen de puente entre la funcionalidad ya existente y la nueva funcionalidad aportada por el plugin.
Estructura de un plugin
El contenido mínimo que un plugin debe incluir se reduce a un fichero XML llamado config.xml. Este fichero contiene el nombre del plugin y un abanico de detalles relevantes sobre el mismo: el directorio que contendrá las librerías aportadas por el plugin, las dependencias que tiene con otros plugins, el nombre base de los ficheros de traducciones, las extensiones que aporta, y una descripción de los componentes que deben añadirse a la interfaz de usuario (menús, barras de herramientas, etc).
Dependiendo de los elementos definidos en el config.xml, el plugin puede incluir también: uno o varios ficheros JAR en el directorio de librerías, varios ficheros de traducciones, imágenes o cualquier otro tipo de datos (cartografía, datos de conexión a algún servicio, paletas de colores, etc)
Podemos ver la estructura del plugin WFS para hacernos una idea de una distribución típica de ficheros:
gvSIG/
extensiones/
com.iver.cit.gvsig.wfs2/
build.number
config.xml
text_en.properties
text_fr.properties
\.\.
text.properites
lib/
com.iver.cit.gvsig.wfs2.jar
images/
backward.png
down-arrow.png
fastbackward.png
fastforward.png
Normalmente, las librerías se depositan en el directorio raíz del plugin, o en un subdirectorio lib/. Las imágenes suelen situarse en un subdirectorio images. Sin embargo, la estructura puede alterarse a nuestro antojo, siempre que en el código usemos la ruta relativa adecuada (en el caso de las imágenes) o definamos correctamente el directorio en el config.xml (en el caso de las librerías).
La filosofía en la que se apoya la extensibilidad de Andami es la siguiente: existen unas clases especiales, llamadas extensiones, que implementan la interfaz IExtension, la cual incluye un método execute() (entre otros). En el fichero config.xml del plugin, se definen una serie de elementos de interfaz de usuario que se van a añadir a la aplicación (herramientas, menús y controles de la barra de estado), y se asocia cada uno de estos elementos con una extensión. En concreto, cuando el usuario pinche en una herramienta o una entrada de menú, Andami ejecutará el método execute de la extensión asociada. De esta forma, la extensión realizará las acciones necesarias para ejecutar la operación solicitada, posiblemente apoyándose en otras clases o librerías del plugin.
Vemos por tanto que la extensión es el punto de acceso a las nuevas funcionalidades aportadas por el plugin, y que los elementos de interfaz de usuario (definidos en el config.xml) junto con la extensión asociada constituyen el nexo entre el resto de la aplicación y el plugin.
Ejemplo de fichero config.xml
<?xml version="1.0" encoding="ISO-8859-1"?>
<plugin-config>
<depends plugin-name="com.iver.cit.gvsig" />
<libraries library-dir="./lib"/>
<resourceBundle name="text"/>
<extensions>
<extension class-name="com.iver.cit.gvsig.wfs.WFSClientExtension"
description="Support to access WFS"
active="true"
priority="1">
</extension>
<extension
class-name="com.iver.gvsig.centerviewpoint.CenterViewToPointExtension"
description="Haces zoom a partir de un par de coordenadas"
active="true">
<menu text="Vista/Centrar_la_Vista_sobre_un_punto"
tooltip="Centrar_la_Vista_sobre_un_punto"
action-command="CENTERVIEWTOPOINT"
icon="images/centerviewtopoint.png" />
<tool-bar name="com.iver.cit.gvsig.Herramientas">
<action-tool icon="images/centerviewtopoint.png"
action-command="CENTERVIEWTOPOINT"
tooltip="Centrar_la_Vista_sobre_un_punto"
enable-text="debería de estar activada" last="true"/>
</tool-bar>
</extension>
</extensions>
</plugin-config>
El fichero utiliza una sintaxis XML, que básicamente implica que tiene una estructura marcada por etiquetas que deben abrirse y posteriormente cerrarse en orden inverso al orden de apertura. Se puede encontrar más información sobre XML en el sitio web del W3 Consortium.
Etiquetas permitidas
A continuación, se describe en detalle cada una de las etiquetas permitidas. No obstante, dentro de la distribución del código fuente de gvSIG, existe un fichero llamado plugin-config.xsd, situado dentro de _fwAndami/schemas, donde se describen formalmente todas las etiquetas permitidas y el tipo de valores que aceptan. Este fichero está escrito en lenguaje XSD Schema, un lenguaje de descripción de sintaxis XML. Ese fichero debe constituir la referencia principal a la hora de conocer las etiquetas permitidas.
Cabecera
El fichero XML debe ir encabezado por una cabecera como la siguiente:
<?xml version="1.0" encoding="UTF-8"?>
Es importante que el primer carácter < sea el primero del fichero (es decir, que no haya ningún carácter en blanco antes de la cabecera), de lo contrario el fichero se leerá incorrectamente.
- version: Indica la versión de estándar XML que vamos a usar (en nuestro caso, "1.0")
- encoding: Indica la codificación de caracteres con la que vamos a salvar el fichero en disco. La codificación que especifiquemos aquí debe coincidir con la codificación que realmente usemos en el momento de salvar el fichero (los editores de texto normalmente permiten especificar la codificación en algún apartado). Algunas codificaciones típicas son "UTF-8", "ISO-8859-15" e "ISO-8859-1"
<plugin-config>
Etiqueta que simplemente marca el inicio y el fin del fichero de configuración; es la raíz del árbol XML.
<resourceBundle>
Indica el nombre base que tendrán los ficheros de traducción. Por ejemplo, si escribimos <resourceBundle name="text"/>, los ficheros de traducciones se llamarán text.properties, text_en.properties, etc.
<depends>
Indica los plugins de los que va a depender nuestro plugins, a nivel de librerías. Esto significa que si deseamos usar algunas clases o librerías que estén en un plugin distinto, debemos declarar una dependencia de ese plugin. Debe haber una entrada <depends> por cada plugin del que dependamos.
Las dependencias determinan también el orden de carga de plugins, de forma que un plugin siempre se carga después de que sus dependencias han sido ya cargadas. Normalmente, todos los plugins dependen del plugin gvSIG (com.iver.cit.gvsig), por lo que este plugin se carga el primero.
<libraries>
Establece el directorio del que se leerán las librerías de nuestro plugin. Normalmente suele usarse <libraries library-dir="."> (para indicar el directorio raíz del plugin), o bien <libraries library-dir="lib"> (para indicar el subdirectorio lib). Dos plugins distintos pueden tener versiones distintas de una misma librería, y ambas funcionarán correctamente sin interferencias, salvo en los siguientes supuestos:
- Si un plugin A declara una dependencia de plugin B, y ambos poseen la misma librería pero en diferente versión: el plugin B usará su propia versión sin ningún problema; el plugin A usará su propia versión si el plugin B se carga después de A, en caso contrario se usará la versión de B.
- Si existe un plugin A que posee una librería, y esta librería está también en el CLASSPATH inicial de Andami (declarado al lanzar Andami), el plugin A usará la versión presente en el CLASSPATH inicial.
<label-set>
Indica el comienzo de un grupo de etiquetas (label) para la barra de estado. Cada grupo de etiquetas (label-set) está asociado a una clase, y sólo es visible cuando la ventana visible es instancia de dicha clase. Acepta los siguientes atributos:
- class-name Nombre de la clase asociada a este label-set. Las etiquetas del label-set sólo serán visibles cuando la ventana activa sea instancia de esta clase.
<label>
Instala una etiqueta (una caja de texto no editable) en la barra de estada. La etiqueta siempre pertenece a un grupo de etiquetas (label-set) y sólo será visible cuando lo sea el label-set. Acepta los siguientes atributos:
- id Identificador (nombre) de la etiqueta. Se usará para acceder a la etiqueta y escribir sobre ella.
- size Anchura de la etiqueta (en píxeles).
<extensions>
Marca el inicio de la lista de extensiones.
<extension>
Declara una extensión de Andami. Principalmente, la declaración incluye el nombre de la clase Java que implementa esta extensión, y una lista de elementos de interfaz de usuario que deberán añadirse a la aplicación y estarán asociados con esta extensión. <extension> acepta los siguientes atributos:
- class-name Nombre de la clase que implementa esta extensión. La clase debe estar localizada en alguna de las librerías incluidas en el plugin.
- description Descripción de la funcionalidad aportada por la extensión.
- active Establece si la extensión va a estar activa o no. Si no está activa, es como si no existiese.
- priority La prioridad de la extensión determina el orden de carga respecto a otras extensiones del mismo plugin. Una extensión con una prioridad menor se cargará antes que otra extensión con una prioridad mayor. Hay que tener en cuenta que las dependencias entre plugin son las que determinan el orden de carga de plugins, por tanto si lo que queremos es cambiar el orden de carga de dos extensiones que están en plugins distintos, debemos modificar las dependencias entre ellos, no la prioridad. Por convención, se usan valores entre 1 y 99999 (aunque realmente se admiten rangos mayores). Para una extensión sin necesidades especiales, podemos omitir el valor prioridad, o darle un valor de 2000.
<tool-bar> Crea una barra de herramientas (grupo de herramientas) con el nombre dado. Dentro esta etiqueta se anidarán los botones que deseamos añadir a esta barra de herramientas. Diferentes extensiones pueden añadir botones a la misma barra de herramientas, basta con que se llamen de la misma forma. <tool-bar> acepta los siguientes atributos:
- name Nombre de la barra de herramientas. Sirve para agrupar botones de extensiones diferentes(si el nombre de la tool-bar es el mismo, se añadirán a la misma tool-bar. También se mostrará en la lista de barras de herramientas, traducido al idioma correspondiente, por lo que name debe considerarse como una clave de traducción.
- position Sirve para establecer la posición de la barra de herramientas respecto a otras. Una tool-bar con un valor de position más bajo que la de otra tool-bar indica que la primera tool-bar se situará más a la izquierda que la segunda. Si diferentes extensiones definen diferentes positions para la misma barra de herramientas, sólo se tendrá en cuenta la position definida en la extensión de mayor prioridad (la que tenga un valor de priority más bajo).
- is-visible Determina si la tool-bar estará inicialmente visible u oculta. Esto puede cambiarse posteriormente por código, o bien por el usuario desde los menús.
<action-tool>
Una action-tool es una herramienta (un botón) que lanza una acción concreta. El mecanismo exacto es el siguiente: el usuario pulsa el botón, Andami recibe el evento de pulsado, consulta la extensión asociada a este botón y lanza el método execute(String actionCommand) de dicha extensión, pasándole como parámetro el comando (action-command) definido para este botón. action-tool acepta los siguientes atributos:
- action-command Especifica el comando que deseamos que sea ejecutado por la extensión asociada. Se puede omitir si la extensión va a tener un solo botón (o menú, etc) asociado, o bien si todos los botones (menús, etc) realizarán la misma acción.
- name Nombre del botón. Útil si posteriormente deseamos obtener el botón para cambiarle el icono, por ejemplo.
- position Sirve para establecer la posición del botón dentro de la barra de herramientas. Un botón con un valor position más bajo que la de otro botón se situará más a la izquierda que el segundo botón. Si diferentes botones poseen la misma posición o no poseen posición, se ordenarán arbitrariamente. Un botón que posea posición siempre irá a la izquierda de un botón sin posición. No obstante, no se recomienda omitir este atributo.
- icon Icono que se mostrará dentro del botón. La ruta de la imagen debe ser relativa al directorio raíz del plugin.
- text Texto (opcional) a mostrar junto al icono.
- tooltip Texto que se mostrará en el globo de texto (tooltip) que aparece al mantener quieto el ratón sobre el botón durante unos segundos.
- enable-text Texto que informa al usuario de las condiciones que deben producirse para que este botón esté activo. Cuando el botón está visible pero desactivado, el tooltip muestra este texto en vez del normal, para que el usuario sepa qué debe hacer para usar esta herramienta.
- last Indica que deseamos dejar un espacio extra a la derecha de este icono. Normalmente se usa en el último icono de una barra de herramientas, para marcar más la separación con la barra de herramientas contigua.
<selectable-tool>
Una selectable-tool es un botón que puede estar pulsado (herramienta seleccionada) o no pulsado (herramienta no seleccionada), y no ejecuta una acción como la action-tool, sino que indica un cambio de estado (por ejemplo, "Zoom Más seleccionado", "Zoom Menos seleccionado", etc). Cuando una selectable-tool está pulsada, las demás están no-pulsadas. Si pulsamos en otra selectable-tool, la anterior deja de estar pulsada. La selectable-tool seleccionada en cada momento va asociada a la ventana activa: si cambiamos de ventana activa, se carga la herramienta seleccionada asociada a la nueva ventana activa. Si volvemos a la ventana anterior, se vuelve a seleccionar la herramienta asociada a la ventana anterior.
La etiqueta selectable-tool admite los mismos atributos que action-tool, casi todos con la misma semántica, salvo action-command que en este caso indica la herramienta seleccionada. Adicionalmente, selectable-tool permite:
- is-default Determina si esta herramienta estará seleccionada inicialmente.
<menu> Crea una entrada de menú. El menú funcionará a todos los efectos como una action-tool, es decir, al pinchar en el menú se lanzará el método execute() de la extensión asociada, pasándole como parámetro el action-command especificado por el menú. Admite los siguientes atributos:
- action-command Especifica el comando que deseamos que sea ejecutado por la extensión asociada. Se puede omitir si la extensión va a tener un solo botón (o menú, etc) asociado, o bien si todos los botones (menús, etc) realizarán la misma acción.
- text Determina la localización y el texto a mostrar por la entrada del menú. text debe tener la forma "Archivo/Abrir". Cada porción separada por barras (/) indica un contenedor de menú, como Archivo, excepto la última porción que indica el texto a mostrar en la entrada misma. Por ejemplo, "Archivo/Abrir" creará una entrada llamada Abrir dentro del menú Archivo. Otro ejemplo: "Archivo/Plantillas/Abrir Plantillas" creará una entrada Abrir Plantillas dentro del submenú Plantillas, dentro del menú Archivo. Si los menús contenedores no existiesen, se crearían automáticamente. Hay que tener en cuenta que el texto real mostrado en los menús y submenús se traduce al idioma seleccionado por el usuario, por lo que cada porción de texto empleado aquí constituye una clave de traducción.
- position Sirve para establecer la posición de la entrada de menú dentro del submenú en el que esté situada. Una entrada con un valor position más bajo que la de otra entrada se situará más arriba (más cerca del inicio del menú) que la segunda entrada. Si diferentes entradas poseen la misma posición, o no poseen posición, se ordenarán arbitrariamente. Una entrada que posea posición siempre estará más arriba que una entrada sin posición. No obstante, no se recomienda omitir este atributo.
- icon Icono que se mostrará dentro del botón. La ruta de la imagen debe ser relativa al directorio raíz del plugin.
- tooltip Texto que se mostrará en el globo de texto (tooltip) que aparece al mantener quieto el ratón sobre el botón durante unos segundos.
- enable-text Texto que informa al usuario de las condiciones que deben producirse para que este botón esté activo. Cuando el botón está visible pero desactivado, el tooltip muestra este texto en vez del normal, para que el usuario sepa qué debe hacer para usar esta herramienta.
- key La key es el carácter que forma la combinación de teclas necesaria para lanzar este menú. La combinación se forma usando el modificador de ese sistema operativo + key. Por ejemplo, en Windows el modificador es la tecla ALT, y la key de la entrada de menú Añadir capa es O, así que pulsando ALT+O nos aparece el diálogo de añadir capa.
- mnemonic El mnemonic es la tecla que activará esta entrada de menú cuando el menú esté desplegado y tenga el foco. Por ejemplo, cuando el menú Archivo está seleccionado, y pulsamos la tecla A, aparece el diálogo de abrir proyecto porque A es el mnemotécnico de la entrada Abrir proyecto.
- is_separator Atributo que indica que deseamos añadir un separador en esta posición de la barra de menús. Es un atributo especial que debe añadirse en una entrada de menú aparte, ya que no añade un separador debajo de la entrada actual, sino que transforma la entrada actual en un separador (y por tanto no se mostrará el texto ni el icono, ni se podrá pulsar sobre ella).
<combo-scale> Añade a la barra de estado un control de tipo combo box (control que permite elegir un valor de una lista de valores, o bien escribir el valor deseado), con una etiqueta de texto a la izquierda describiendo su función. Acepta los siguientes atributos:
- name Nombre del control. Debería ser diferente al nombre de cualquier otro control.
- label El texto que será mostrado a la izquierda del combo box.
- elements Una lista de valores numéricos separados por punto y coma. Cada valor constituirá una entrada del combo box.
- value Valor que estará inicialmente seleccionado. Si se omite, el primer elemento estará seleccionado.
- action-command Especifica el comando que deseamos que sea ejecutado por la extensión asociada. Se puede omitir si la extensión va a tener un solo botón (o menú, etc) asociado, o bien si todos los botones (menús, etc) realizarán la misma acción.
La interfaz IExtension
Como ya hemos explicado, las extensiones son clases que sirven de puente entre la funcionalidad básica de gvSIG y las funcionalidades aportadas por nuestro plugin. La extensión recibirá notificación de las acciones realizadas por el usuario, y ejecutará el código apropiado en respuesta a esas acciones (posiblemente usando otras clases de nuestro plugin).
El siguiente diagrama muestra de forma esquemática todas las clases que vamos a explicar en este capítulo y sus relaciones:
Todas las extensiones implementan la interfaz IExtension, que incluye los siguientes métodos:
- public void initialize() Una vez se leen y ordenan todas las extensiones presentes en los plugins, se llama a este método para cada una de ellas, de forma secuencial siguiendo el orden definido en la prioridad de las extensiones.
- public void postInitialize() Una vez se han inicializado todas las extensiones, se llama a este método para cada una de ellas, siguiendo el mismo orden de antes.
- public void terminate() Al salir de la aplicación, se llama a este método para cada una de las extensiones, siguiendo un orden secuencial inverso al que se siguió en la inicialización (es decir, la última extensión que se inicializó es la primera que se finalizará, y la primera extensión que se inicializó será la última en ser finalizada).
- public void execute(String actionCommand) En el config.xml se definen elementos de la interfaz de usuario (botones, menús, etc) que están asociados a una extensión. Cuando el usuario pulsa en uno de estos elementos de la interfaz, se busca la extensión asociada y se llama a este método. Los botones, menús, etc pueden tener asociado una cadena que actúa como identificador de comando (action command). Cuando se llama al método execute, se pasa como parámetro el actionCommand asociado al botón que se pulsó. De esta forma, si tenemos varios botones, menús, etc asociados con esta extensión, podemos discriminarlos comprobando el actionCommand que recibimos. Normalmente, será en este método donde escribiremos las principales acciones de nuestra extensión (abrir un asistente, comenzar algún procesamiento, etc).
Los dos siguientes métodos determinan si la extensión (y sus elementos de interfaz de usuario asociados) serán visibles y estarán activos. Estas propiedades se comprueban para todas las extensiones cada vez que se cambia de ventana activa y también justo después de procesar un evento de interfaz de usuario (es decir, después de llamar al método execute de una extensión).
- public boolean isEnabled() Los elementos de interfaz de usuario (botones, menús, controles de la barra de estado) asociados a esta extensión estarán activados (o desactivados) dependiendo del valor devuelto por este método. Cuando un botón está desactivado, aparece en gris claro y no es posible pulsar sobre él.
- public boolean isVisible() Los elementos de interfaz de usuario (botones, menús, controles de la barra de estado) asociados a esta extensión estarán visibles (o invisibles) dependiendo del valor devuelto por este método. Cuando un botón está invisible, no se mostrará independientemente del valor de isEnabled().
Los dos métodos siguientes sólo se usan con el mecanismo ExclusiveUIExtension, que permite a una extensión tomar el control de la interfaz y activar o desactivar (mostrar u ocultar) otras extensiones. Esto puede usarse para crear extensiones que personalicen gvSIG (por ejemplo, una extensión que transforme gvSIG en un simple visor de cartografía):
- public boolean isEnabled(IExtension extension) Decide si la extensión pasada como parámetro estará activada o no, ignorando el valor del método isEnabled de la propia extensión.
- public boolean isVisible(IExtension extension) Decide si la extensión pasada como parámetro estará visible o no, ignorando el valor del método isVisible de la propia extensión.
La clase abstracta Extension
Salvo que tengamos una necesidad muy especial, normalmente no implementaremos directamente la interfaz IExtension, sino que extenderemos la clase abstracta Extension, que a su vez ya implementa IExtension. Esto nos ofrece una cierta tranquilidad de futuro en el funcionamiento de nuestra extensión, ya que si se añadiese algún método a la interfaz IExtension, este método se implementaría en la clase Extension de forma que nuestra extensión seguiría funcionando sin cambios (a no ser que necesitase un comportamiento para el nuevo método distinto del implementado en Extension).
HiddableExtension y ExtensionDecorator
La interfaz HiddableExtension y la clase ExtensionDecorator permiten cambiar la visibilidad de una extensión (y sus controles asociados) en tiempo de ejecución, ignorando el valor que indique la propia extensión en el método isVisible. De esta forma podemos instalar una extensión que oculte un conjunto de otras extensiones, creando un gvSIG personalizado (que sólo mostraría las extensiones deseadas).
La interfaz HiddableExtension sólo contiene dos métodos:
- public void setVisibility(int state) Permite definir una visibilidad para una extensión, que prevalece sobre la visibilidad definida en el método isVisible() de la propia extensión. Los valores de visibilidad aceptados incluyen: ExtensionDecorator.INACTIVE (el Decorator está desactivado y por tanto se obedecerá a la establecido en el método isVisible() de la propia extensión), ExtensionDecorator.ALWAYS_VISIBLE (la extensión estará siempre visible) y ExtensionDecorator.ALWAYS_INVISIBLE (la extensión estará siempre invisible).
- public int getVisibility() Consulta la visibilidad que se ha adjudicado a la extensión. Los valores devueltos pueden ser ExtensionDecorator.INACTIVE, ExtensionDecorator.ALWAYS_VISIBLE o ExtensionDecorator.ALWAYS_INVISIBLE.
La clase ExtensionDecorator implementa la interfaz HiddableExtension. Cada extensión cargada por Andami posee un ExtensionDecorator asociado, que podemos obtener para cambiar la visibilidad de esa extensión. Mostramos como ejemplo el código necesario para ocultar la extensión "com.iver.cit.gvsig.StartEditing", que es la que permite poner una capa en edición. De esta forma, estaríamos desactivando las capacidades de edición de gvSIG:
ExtensionDecorator decorator = PluginServices.getDecoratedExtension(com.iver.cit.gvsig.StartEditing.class);
decorator.setVisibility(ExtensionDecorator.ALWAYS_INVISIBLE);
Por supuesto, para llevar a cabo con éxito una personalización de gvSIG, necesitamos conocer las extensiones que deseamos ocultar. Además, para poder acceder correctamente a la clase de la extensión a desactivar (en este ejemplo, com.iver.cit.gvsig.StartEditing.class) necesitamos declarar una dependencia del plugin que contenga la extensión, de lo contrario obtendremos una excepción de tipo ClassNotFoundException al ejecutar ese código. En este caso, com.iver.cit.gvsig.StartEditing.class está contenido en el plugin com.iver.cit.gvsig.cad, por tanto necesitamos añadir al config.xml de nuestro plugin una línea como la siguiente:
<depends plugin-name="com.iver.cit.gvsig.cad" />
Téngase en cuenta que cualquier extensión puede usar este mecanismo en cualquier momento para ocultar o mostrar otra extensión. Para evitar comportamientos impredecibles, ExtensionDecorator sólo debería usarse en extensiones especiales que deseen personalizar gvSIG, y no deberían instalarse dos extensiones de este tipo al mismo tiempo (de lo contrario, es posible que lo que una de ellas vaya ocultando, la otra lo vaya mostrando y será difícil prever el resultado final, salvo que ambas extensiones realicen estas ocultaciones de forma coordinada).
El mecanismo ExclusiveUIExtension
El mecanismo ExclusiveUIExtension permite realizar algo similar a lo que se consigue con ExtensionDecorator, pero en este caso existe una única extensión que decidirá el estado de las demás. Es decir, con ExtensionDecorator cualquier extensión puede modificar el estado de las demás. Con ExclusiveUIExtension, se definirá una única extensión que tendrá la capacidad de mostrar/ocultar y activar/desactivar el resto de extensiones.
En cualquier caso, ambos mecanismos pueden usarse al mismo tiempo y ExclusiveUIExtension respetará lo definido por ExtensionDecorator, de forma que si ExtensionDecorator define una visibilidad ALWAYS_VISIBLE (siempre visible), la extensión estará visible pese a que ExclusiveUIExtension defina la extensión como oculta. Es decir, ExclusiveUIExtension sólo actuará en caso de que el ExtensionDecorator esté inactivo (modo ExtensionDecorator.INACTIVE). En cualquier caso, ExtensionDecorator sólo actúa sobre la visibilidad de la extensión, mientras que el mecanismo ExclusiveUIExtension puede definir también si la extensión está activa (enabled) o no.
Para usar ExclusiveUIExtension necesitamos:
- Tener una extensión que implemente correctamente los métodos isEnabled(IExtension extension) y isVisible(IExtension extension). Esta extensión actuará como ExclusiveUIExtension, decidiendo el estado (visible/invisible, activa/desactivada) del resto de extensiones.
- Registrarse como ExclusiveUIExtension en Andami, dentro del método initialize de nuestra extensión, utilizando el método PluginServices.setExclusiveUIExtension().
- Otra opción equivalente a registrarnos utilizando el método setExclusiveUIExtension consiste en sumistrar un parámetro de arranque a gvSIG. El parámetro es el siguiente: ExclusiveUIExtension=NombreDeExtension, donde NombreDeExtension debe sustituirse por el nombre de la extensión mencionada en el punto anterior.
- En Linux, esto puede conseguirse bien editando el lanzador gvSIG.sh añadiéndole este parámetro antes del símbolo "$@", o bien pasándole el parámetro a dicho lanzador.
- En Windows se puede modificar el fichero gvSIG.ini, en la línea que empieza por command, añadiendo el parámetro justo antes del símbolo #ARGS#. También es posible pasar el parámetro al comando gvSIG.exe en el momento de su ejecución.
Andami facilita la creación y gestión de ventanas, ofreciendo una interfaz de ventana que se abstrae de los diferentes tipos de ventanas de Java Swing. De esta forma, cuando queramos crear una ventana, en vez de pensar en ventanas de Swing, simplemente crearemos un panel que implemente la interfaz IWindow y le pediremos a Andami que cree una ventana con las propiedades que deseemos. En función de las propiedades pedidas y el skin instalado, se creará un tipo de ventana distinto (JInternalFrame, JDialog, etc), pero esto será indiferente para nosotros. Para no confundir la ventana real (creada por Andami) con el contenido (creado por nosotros y que implementa la interfaz IWindow), de ahora en adelante usaremos los términos frame , marco o ventana real para referirnos a la ventana creada por Andami, y ventana para referirnos al contenido (que implementa IWindow).
Cuando hablamos de ventanas de Andami, podemos hacer una primera distinción entre ellas: ventana principal, y resto de ventanas. La ventana principal (MainFrame) es la que contiene las barras de herramientas, la barra de estado y (en algunas plataformas) la barra de menús. Esta ventana no está bajo control del gestor de ventanas, sino que se accede a ella usando un método específico de la clase PluginServices. El resto de ventanas sí están bajo el control del gestor de ventanas (MDIManager), al cual debemos dirigirnos para crearlas, cerrarlas o cambiar sus propiedades.
- En el capítulo Gestión de ventanas podemos aprender como crear ventanas y cómo manipularlas haciendo uso del gestor de ventanas (MDIManager).
- En el capítulo La ventana principal podemos encontrar más información sobre las posibilidades ofrecidas por dicha ventana (MainFrame).
MainFrame
La interfaz MainFrame representa la ventana principal de gvSIG, la ventana a la que se añaden los menús, la barras de herramientas, etc. La clase que la implementa (MDIFrame) hereda de JFrame, e incluye el JDesktopPane que se usa para contener las ventanas internas (vistas, mapas, etc). Normalmente, Andami crea automáticamente la interfaz de usuario partiendo de los ficheros config.xml de los plugins. No obstante, MainFrame contiene algunos métodos para modificar por código algunas partes de la interfaz.
- Los métodos addMenu(), changeMenuName() y removeMenu() permiten alterar los menús de gvSIG.
- El método getJMenuBar() da acceso a la barra de menús, lo cual también permite alterar los menús.
- El método getStatusBar() permite acceder a la barra de estado.
- setTitle() cambia el título de la ventana.
- setStatusBarLabels(Class clase, Label[] labels) y removeStatusBarLabels(Class clase) añaden o eliminan etiquetas de la barra de estado, respectivamente. Las etiquetas se asocian a una clase (objeto tipo Class), y serán visibles cuando la ventana activa sea un objeto de esa clase.
- getComponentByName(String name) permite obtener un componente a partir de su nombre. De esta forma podemos obtener barras de herramientas, controles de la barra de herramientas o de la barra de estado, y menús.
- addStatusBarControl() y removeStatusBarControl() nos permiten añadir controles personalizados a la barra de estado. Serán visibles y estarán activos cuando la extensión asociada sea visible y esté activa.
La barra de estado: NewStatusBar
La barra de estado es la banda que se muestra en la parte inferior de la ventana principal, y que contiene etiquetas y algunos controles. Es posible cambiar el texto de las etiquetas existentes, así como añadir otras etiquetas o controles.
- public void setMessage(String id, String nuevoTexto)
Cambia el texto mostrado en la etiqueta id por nuevoTexto. id se debe corresponder con el atributo id de una etiqueta definida en el config.xml o si se trata de un control, con el nombre del control.
Muestra un mensaje de información en la parte izquierda de la barra de estado, siempre que actualmente no se esté mostrando un mensaje temporal. Si se está mostrando un mensaje temporal, el mensaje establecido ahora se mostrará cuando se llame al método restaurarTexto().
- setWarningText(String text)
Muestra un mensaje de aviso en la parte izquierda de la barra de estado, siempre que actualmente no se esté mostrando un mensaje temporal. Si se está mostrando un mensaje temporal, el mensaje establecido ahora se mostrará cuando se llame al método restaurarTexto().
- setErrorText(String text)
Muestra un mensaje de error en la parte izquierda de la barra de estado, siempre que actualmente no se esté mostrando un mensaje temporal. Si se está mostrando un mensaje temporal, el mensaje establecido ahora se mostrará cuando se llame al método restaurarTexto().
- setInfoTextTemporal(String text), setWarningTextTemporal(String text), setErrorTextTemporal(String text) y restaurarTexto()
Muestran un mensaje de información, aviso u error (respectivamente), pero sólo de forma temporal, ya que si a continuación usamos el método restaurarTexto() se vuelve a mostrar el mensaje anterior.
- setProgress(int progress)
Si progress está entre 0 y 99 (inclusive), muestra una barra de progreso en la parte izquierda y avanza la posición a ese valor. Si progress es mayor o igual que 100, oculta la barra de progreso.
Mientras la gestión de plugins se incorpora directamente en Andami, la gestión de ventanas se delega en los llamados skins de Andami. Andami ofrece unas interfaces y objetos para la gestión de ventanas: IWindow, MDIManager, WindowInfo, IWindowListener, SingletonWindow... dichas interfaces deben ser implementadas por el skin, quien realmente realizará la gestión de ventanas. De esta forma, se podría llevar a cabo una gestión de ventanas distinta, implementando un skin distinto. Actualmente sólo existe un Skin implementado, llamado CorePlugin. Formalmente, es un plugin más de Andami, pero con la particularidad de que Andami no arrancará si CorePlugin (u otro skin) no está instalado. Los skins deben incluir una extensión especial, que implemente IExtension y MDIManager, y que se define en el config.xml con la etiqueta skin-extension.
La interfaz IWindow
Cualquier ventana que deseemos crear a través de Andami debe implementar la interfaz IWindow. Conviene aclarar que IWindow no será una ventana de Swing, sino el contenido de la ventana (normalmente una clase que herede de JPanel a la que añadiremos todos los componentes necesarios). La ventana propiamente dicha (frame), será creada por Andami y no tendremos acceso directo a ella.
La interfaz IWindow sólo posee un método, que especifica las propiedades iniciales que deberá tener la ventana creada (posición, tamaño, tipo, título, etc.):
public WindowInfo getWindowInfo()
Este método será usado por Andami en el momento de la creación de la ventana real para determinar las propiedades y el tipo de ventana a crear. El objeto devuelto por este método es de tipo WindowInfo, el cual describe las propiedades de una ventana de Andami (explicado en la sección siguiente). Debemos hacer hincapié en que este método sólo devuelve las propiedades iniciales y que por tanto no debemos usar este método para obtener o modificar las propiedades de la ventana, para esta tarea nos dirigiremos al gestor de ventanas.
La mayoría de las ventanas que creemos no implementarán directamente IWindow, sino una interfaz más específica, SingletonWindow, que asocia un modelo a cada ventana. Este modelo se utiliza para dotar de identidad a las ventanas, de forma que al intentar crear una ventana con un modelo que ya esté asociado a otra ventana, obtendremos una referencia a esta segunda ventana.
Propiedades de ventana: WindowInfo
El objeto WindowInfo almacena las propiedades de una ventana de Andami, de forma que existe un objeto WindowInfo asociado a cada ventana (IWindow) creada por Andami. Puede usarse tanto para consultar las propiedades actuales de la ventana como para modificarlas. Al modificar una propiedad de WindowInfo, este ventana se refleja inmediatamente en la ventana asociada.
Atendiendo el comportamiento que tendrá la ventana respecto a las otras, existen ventanas de tres tipos: normales, modales y modeless.
- Llamamos ventana modal a aquella que siempre están encima de las otras y además no permite que el foco pase a ninguna de las otras. Cuando creamos una ventana modal, no podremos interactuar con los menús, herramientas, etc., de la ventana principal hasta que cerremos la ventana modal (ni tampoco podremos interactuar con el resto de ventanas). Además, las ventanas modales suspenden el hilo de ejecución principal de la aplicación, de forma que el código de nuestra extensión no continuará ejecutándose hasta que cerremos la ventana modal.
- Llamamos ventana modeless a aquella que siempre está encima de las otras pero a diferencia de la modal sí permite que el foco pase a otras ventanas, y por tanto permite interactuar con los menús y herramientas de la ventana principal y con el contenido de otras ventanas.
- Ventanas normales son las que pueden estar encima o debajo de las otras de forma normal, es decir, las que no imponen ninguna restricción especial respecto al foco o a la superposición a otras ventanas.
Debemos tener en cuenta que las ventanas modeless sólo lo serán respecto a las ventanas normales; es decir, una ventana modeless siempre estará encima de las ventanas normales existentes, pero podrá estar encima o debajo de otra ventana modeless, dependiendo de qué ventana seleccionemos.
De forma similar, las ventanas modales sólo lo serán respecto a las ventanas normales o modeless; es decir, siempre estarán encima de las ventanas normales o modeless existentes, pero podrán estar encima o debajo de otras ventanas modales, dependiendo de qué ventana esté seleccionada.
El tipo de ventana que se creará a partir de un objeto WindowInfo depende de las propiedades suministradas al constructor del objeto. Por ejemplo, para crear una ventana modal, el método getWindowInfo() de la ventana deberá contener el siguiente código:
WindowInfo windowInfo = new WindowInfo(WindowInfo.MODALDIALOG);
return windowInfo;
Para crear una ventana modeless:
WindowInfo windowInfo = new WindowInfo(WindowInfo.MODELESSDIALOG);
return windowInfo;
Para crear una ventana normal:
WindowInfo windowInfo = new WindowInfo();
return windowInfo;
Existen otras propiedades que podemos especificar en el constructor, que añadiremos usando el operador de suma binaria |:
- WindowInfo.RESIZABLE Especifica que se permitirá al usuario redimensionar la ventana. Si no se incluye, la ventana no será redimensionable (tendrá tamaño fijo).
- WindowInfo.MAXIMIZABLE Especifica que la ventana podrá ser maximizable. Una ventana maximizable posee dos modos: modo maximizado, en el cual se empotra en la ventana principal de Andami, ocupando todo su espacio, y modo no maximizado, en el cual la ventana posee su tamaño normal. Para pasar de un modo a otro, el usuario debe pulsar el botón de Maximizar/Restaurar (en la barra del título). El objeto WindowInfo posee también unos métodos para cambiar este estado a nivel de código. Si no se especifica la propiedad MAXIMIZABLE, la ventana no será MAXIMIZABLE (siempre estará en el estado no maximizado.
- WindowInfo.ICONIFIABLE Especifica que se permitirá al usuario minimizar la ventana. Cuando la ventana se minimiza, desaparece y en su lugar aparece un botón en la parte inferior izquierda de la ventana principal. Al pulsar en este botón, la ventana vuelve a su estado normal.
- WindowInfo.PALETTE Algunas ventanas están diseñadas para poder ser empotradas dentro de otras. Estas ventanas tienen dos modos: modo empotrado y modo paleta (flotante). Cuando están en modo paleta, el contenido se muestra en una ventana independiente, de tipo MODELESSDIALOG. Cuando están en modo empotrado, el contenido está integrado en algún área de otra ventana. El localizador gráfico, por ejemplo, es una herramienta que es capaz de mostrarse en una ventana independiente o bien empotrado en la parte inferior izquierda de una vista. Las ventanas tipo paleta deben implementar la interfaz IWindowTransform.
- Además, existen los dos atributos que ya hemos explicado anteriormente: WindowInfo.MODALDIALOG y WindowInfo.MODELESSDIALOG.
Para crear una ventana modeless redimensionable y maximizable:
WindowInfo windowInfo = new WindowInfo(WindowInfo.MODELESSDIALOG | WindowInfo.RESIZABLE | WindowInfo.MAXIMIZABLE);
return windowInfo;
No es posible utilizar simultáneamente las propiedades MODELESSDIALOG y MODALDIALOG. Además de que no tiene sentido, si lo hacemos obtendremos un error de Andami.
Hasta aquí hemos visto las propiedades que acepta el constructor de la clase WindowInfo, y que determinan el comportamiento de la ventana. Además de esto, existe una serie de métodos que permiten consultar diversas propiedades de la ventana, así como modificarlas:
Devuelve la coordenada X (en píxeles) del vértice superior izquierdo de la ventana. El origen de coordenadas (punto 0, 0) se sitúa en la parte superior izquierda de la ventana principal, justo debajo de la barra de herramientas.
Establece la coordenada X (en píxeles) del vértice superior izquierdo de la ventana.
Devuelve la coordenada Y (en píxeles) del vértice superior izquierdo de la ventana. El origen de coordenadas (punto 0, 0) se sitúa en la parte superior izquierda de la ventana principal, justo debajo de la barra de herramientas.
Establece la coordenada Y (en píxeles) del vértice superior izquierdo de la ventana.
Cuando la ventana no está maximizada, obtiene la coordenada X (en píxeles) del vértice superior izquierdo. Cuando está maximizada, devuelve la coordenada X que tendría la ventana si no estuviese maximizada.
- public void setNormalX(int x)
Establece la coordenada X (en píxeles) del vértice superior izquierdo de la ventana cuando la ventana está no-maximizada. Si la ventana está maximizada actualmente, este método no cambia su estado, pero al restaurar la ventana al estado normal la coordenada X será la establecida aquí.
Cuando la ventana no está maximizada, obtiene la coordenada Y (en píxeles) del vértice superior izquierdo. Cuando está maximizada, devuelve la coordenada Y que tendría la ventana si no estuviese maximizada.
- public void setNormalY(int y)
Establece la coordenada y (en píxeles) del vértice superior izquierdo de la ventana cuando la ventana está no-maximizada. Si la ventana está maximizada actualmente, este método no cambia su estado, pero al restaurar la ventana al estado normal la coordenada X será la establecida aquí.
Devuelve la anchura (en píxeles) de la ventana.
- public void setWidth(int width)
Establece la anchura (en píxeles) de la ventana.
Devuelve la altura (en píxeles) de la ventana.
- public void setHeight(int height)
Establece la altura (en píxeles) de la ventana.
- public int getNormalWidth()
Cuando la ventana no está maximizada, obtiene la anchura (en píxeles) de la ventana. Cuando está maximizada, devuelve la anchura que tendría la ventana si no estuviese maximizada.
- public void setNormalWidth(int width)
Establece la anchura (en píxeles) de la ventana cuando no está minimizada. Si la ventana está maximizada actualmente, este método no cambia su estado, pero al restaurarla al estado normal la ventana tendrá la anchura establecida aquí.
- public int getNormalHeight()
Cuando la ventana no está maximizada, obtiene la altura (en píxeles) de la ventana. Cuando está maximizada, devuelve la altura que tendría la ventana si no estuviese maximizada.
- public void setNormalHeight(int height)
Establece la altura (en píxeles) de la ventana cuando no está minimizada. Si la ventana está maximizada actualmente, este método no cambia su estado, pero al restaurarla al estado normal la ventana tendrá la altura establecida aquí.
- public Rectangle getBounds()
Obtiene la posición y dimensiones (en píxeles) de la ventana.
- public void setBounds(Rectangle bounds)
Establece la posición y dimensiones (en píxeles) de la ventana.
- public Rectangle getNormalBounds()
Cuando la ventana no está maximizada, obtiene la posición y dimensiones (en píxeles) de la ventana. Cuando está maximizada, devuelve la posición y dimensiones que tendría la ventana si no estuviese maximizada.
- public void setBounds(Rectangle bounds)
Establece la posición y dimensiones (en píxeles) de la ventana cuando está no-maximizada. Si la ventana está maximizada actualmente, este método no cambia su estado, pero al restaurarla al estado normal la ventana tendrá la posición y dimensiones establecidas aquí.
- public String getSelectedTool()
Obtiene el actionCommand la herramienta seleccionada asociada a la ventana actual (ver la descripción de <selectable-tool> para conocer el mecanismo de herramientas seleccionables).
Obtiene el título de la ventana.
- public void setTitle(String title)
Establece el título de la ventana.
Obtiene el identificador de ventana.
- public void setId(int id)
Establece el identificador de ventana.
- public boolean isClosed()
Devuelve cierto (true) si la ventana está cerrada, falso (false) en caso contrario.
- public void setClosed(boolean closed)
Establece si la ventana está abierta o cerrada. La ejecución de este método no cambia el estado de la ventana, use PluginServices.getMDIManager().addWindow(window) y PluginServices.getMDIManager().closeWindow(window) para abrir o cerrar una ventana.
- public boolean isIconfiable()
Devuelve cierto (true) si la ventana es minimizable, falso (false) en caso contrario.
- public boolean isMaximizable()
Devuelve cierto (true) si la ventana es maximizable, falso (false) en caso contrario.
- public void setMaximizable(boolean maximizable)
Establece si la ventana es maximizable o no. La ejecución de este método no cambia el estado de la ventana una vez se ha creado, se debe definir si la ventana es maximizable antes de crearla.
- public boolean isMaximized()
Devuelve cierto (true) si la ventana está maximizada, falso (false) en caso contrario.
- public void setMaximized(boolean maximized)
Maximiza o restaura la ventana dependiendo del parámetro maximized.
Devuelve cierto (true) si la ventana es modal, falso (false) en caso contrario.
- public boolean isModeless()
Devuelve cierto (true) si la ventana es modeles, falso (false) en caso contrario.
- public boolean isPalette()
Devuelve cierto (true) si la ventana está actualmente en modo paleta (flotante), falso (false) si está empotrada en la ventana correspondiente.
- public void toPalette(boolean b)
Establece si la ventana está en modo paleta o no. La ejecución de este método no cambia el estado de la ventana, simplemente actualiza el objeto WindowInfo. Use la interfaz IWindowTransform para pasar una ventana a modo paleta o restaurarla a modo empotrado.
- public boolean isResizable()
Devuelve cierto (true) si la ventana es redimensionable, falso (false) en caso contrario.
- public boolean isVisible()
Devuelve cierto (true) si la ventana es visible, falso (false) en caso contrario.
- public void setPersistence(boolean persist)
El tamaño, la posición y el estado de las ventanas se almacena en los proyectos de gvSIG, esta información se usa al abrir un proyecto para restaurar las ventanas a su estado anterior. Es posible que no deseemos que se almacene el estado de una ventana concreta (por ejemplo, porque inicialmente deba estar siempre cerrada o en una posición exacta). Este método establece si el estado de esta ventana debe guardarse en los proyectos de gvSIG o debe ser ignorada.
- public boolean checkPersistence()
Devuelve cierto (true) si el estado de esta ventana debe guardarse en los proyectos de gvSIG, falso (false) si debe ser ignorada.
- public XMLEntity getXMLEntity()
Obtiene un árbol XML que almacena las propiedades de este objeto WindowInfo. Es útil para salvar las propiedades de la ventana.
public static WindowInfo createFromXMLEntity(XMLEntity xml)
Método estático que crea un objeto WindowInfo a partir de las propiedades almacenadas en el árbol XML suministrado.
El gestor de ventanas: MDIManager
La interfaz MDIManager nos permite interactuar con las ventanas. Es el punto al que debemos dirigirnos para crear ventanas, cerrarlas, cambiarles el tamaño, obtener la ventana activa, obtener la lista de todas las ventanas, etc. Durante la creación de ventanas, se encarga de crear las ventanas con unas propiedades homogéneas (teniendo en cuenta, eso sí, las propiedades que hemos solicitado) y registrarlas en el menú Ventana, entre otras cosas. También se encarga de escuchar los cambios producidos en los objetos WindowInfo para reflejar estos cambios en la ventana real asociada.
Como ya se ha comentado, la gestión de ventanas de Andami es bastante particular porque el programador sólo debe crear un panel con el contenido de la ventana, y MDIManager creará la ventana real. Por esta razón, para cambiar las propiedades de una ventana debemos dirigirnos a MDIManager, que es quien conoce la implementación real de la ventana (frame).
MDIManager posee un amplio rango de métodos para interactuar con las ventanas. La mayoría de ellos requieren una referencia al objeto IWindow que deseamos alterar. Otros, por contra, devuelven una referencia a tal objeto.
Métodos para crear ventanas
- public IWindow addWindow(IWindow panel)
Crea un nuevo frame cuyo contenido será panel. Las propiedades iniciales de la ventana creada (tamaño, tipo, etc), vendrán determinadas por el método getWindowInfo() de panel. Si la ventana es de tipo MODAL, se creará en posición centrada, ignorando la posición especificada en getWindowInfo. Si la ventana es de tipo SingletonWindow y ya existe una ventana con el mismo modelo, no se creará una ventana nueva sino que la ventana ya existente se traerá a primer plano y se le dará el foco. En este caso se devolverá una referencia a dicha ventana ya existente.
- public IWindow addCentredWindow(IWindow panel)
Método idéntico a addWindow, salvo que crea todas las ventanas en posición centrada, ignorando la posición especificada por el método getWindowInfo() de panel.
Métodos para cerrar ventanas
- public void closeAllWindows()
Cierra todas las ventanas abiertas.
- public void closeWindow(IWindow window)
Cierra la ventana window.
- public boolean closeSingletonWindow(Object model)
Cierra la ventana Singleton que tenga como modelo el objeto model.
- public boolean closeSingletonWindow(Class viewClass, Object model)
Cierra la ventana Singleton cuya clase y modelo se pasan como parámetros.
Métodos para obtener las ventans existentes
- public IWindow[] getAllWindows()
Obtiene un vector que contiene todas las ventanas que están abiertas actualmente, incluyendo las minimizadas y maximizadas. La ventana principal no está incluída, ya que no está bajo el control de MDIManager.
- public IWindow[] getOrderedWindows()
Obtiene un vector que contiene todas las ventanas que están abiertas actualmente, ordenado según la profundidad (posición Z) de las ventanas. Es decir, la primera ventana del vector será la ventana que esté en primer plano (delante de las demás) y la última ventana del vector será la que esté más al fondo (detrás de las demás).
Devuelve la ventana activa en este momento, excluyendo las ventanas modales y las ventanas tipo PALETTE. Si la ventana activa actual es modal o tipo PALETE, devuelve la última ventana activa que no fuese modal ni tipo PALETTE. Esto es así porque se considera que las ventanas modales y tipo PALETTE no están al mismo nivel que las demás, sino que son ventanas auxiliares (normalmente contienen algún tipo de herramienta para manipular el contenido de otra ventana o presentan alguna información puntual).
Devuelve la ventana que posee el foco en este momento, excluyendo las ventanas modales. Si la ventana que posee el foco actualmente es modal, devuelve la última ventana no-modal que tuvo el foco.
Métodos para modificar propiedades de ventanas
- public WindowInfo getWindowInfo(IWindow window)
Obtiene el objeto WindowInfo asociado a la ventana window. Este objeto contiene información actualizada sobre la posición, tamaño, título, etc. Además de proporcionar información, este objeto puede utilizarse para hacer cambios en las propiedades de la ventana (tamaño, posición, etc). En Propiedades de ventana: WindowInfo se describe en detalle las propiedades que se pueden modificar usando este objeto.
- public synchronized void centreWindow(IWindow panel)
Centra la ventana panel.
- public void changeWindowInfo(IWindow window, WindowInfo wi)
Actualiza las propiedades de la ventana window usando los valores especificados en el objeto wi.
- public void refresh(IWindow win)
Normalmente, las ventanas se redibujan automáticamente cuando corresponde, de acuerdo al layout asignado y a los cambios de tamaño, etc. Sin embargo, si queremos forzar un redibujado, podemos hacerlo usando este método.
Otros métodos de utilidad
- public void setWaitCursor()
Muestra el icono de espera como cursor del ratón, y bloquea todos los eventos que se produzcan en la ventana principal hasta que se reciba una llamada a restoreCursor().
- public void restoreCursor()
Restaura el icono de ratón que hubiese antes de comenzar la operación, y reactiva el manejo de eventos de la ventana principal.
- public void setBackgroundImage(ImageIcon image, String typeDesktop)
Establece la imagen proporcionada como fondo de la ventana principal. El argumento typeDesktop debe tomar uno de los siguientes valores: Theme.CENTERED, Theme.EXPAND, Theme.MOSAIC (para indicar que la imagen debe situarse bien centrada, bien debe expandirse para ocupar todo el tamaño de la ventana, o bien debe repetirse formando un mosaico hasta llenar todo el espacio).
La interfaz SingletonWindow
Existe un tipo de especial de ventanas de Andami: las ventanas Singleton, ventanas que tienen asociado un modelo. El modelo puede ser cualquier objeto Java que elijamos, y nos sirve para dotar de identidad a la ventana. De esta forma, existirá una única ventana asociada a un modelo, y podremos hacer referencia a la ventana a través del modelo. Si intentamos añadir una ventana Singleton cuyo modelo ya está asociado a otra ventana existente, en vez de crearse una nueva ventana se mandará la ventana existente a primer plano, y obtendremos una referencia a dicha ventana.
La interfaz Singleton cuenta simplemente de un método:
public Object getWindowModel()
Devuelve el modelo de esta ventana Singleton. El modelo debería ser constante en el tiempo para una ventana dada, salvo que realmente queramos cambiar la identidad de la ventana.
La interfaz SingletonWindow extiende la interfaz IWindow, por lo que cualquier ventana que implemente SingletonWindow implementará también IWindow.
La interfaz IWindowListener
La interfaz IWindowListener permite a las ventanas que la implementen registrarse como listener (oyente) de los eventos que suceden sobre sí mismas.
public void windowActivated()
Este método se ejecutará cada vez que se active la ventana.
public void windowClosed()
Este método se ejecutará cuando la ventana se esté cerrando.
Para recibir estos eventos, basta con que las ventanas implementen esta interfaz, Andami lo detectará y les enviará los eventos correspondientes sin necesidad de seguir ningún paso adicional.
La interfaz IWindowTransform
Las ventanas tipo PALETTE (ventanas que pueden empotrarse dentro de otras o bien estar en modo flotante o paleta) deben implementar esta interfaz, para pasar correctamente de modo paleta a modo empotrado.
Esta interfaz posee tres métodos, que la ventana debe implementar correctamente de acuerdo a su contexto (ya que cada herramienta adoptará una forma particular en modo empotrado y en modo flotante):
Pide a la ventana que se transforme en modo flotante.
Pide a la ventana que se transforme en modo empotrado.
Devuelve cierto si la ventana está en modo paleta (flotante), falso si está en modo empotrado.
Andami proporciona una variedad de servicios a los plugins, casi todos a través de la clase PluginServices. Estos servicios cubren varias tareas que se realizan con frecuencia en cualquier parte de la aplicación, y por tanto conviene ofrecer una forma sencilla de realizarlas.
Cada plugin tiene un objeto PluginServices asociado, que podemos obtener con la llamada:
PluginServices.getPluginSerivices(pluginName)
o bien
PluginServices.getPluginServices(object),
donde object debe corresponderse con algún objeto perteneciente al plugin. Este objeto PluginServices permite acceder a servicios específicos del plugin, como obtención de recursos (imágenes, etc), el nombre del plugin, persistencia de datos, etc. Existen otros servicios no asociados específicamente a cada plugin que son accesibles a través de métodos estáticos de esta misma clase.
Los plugins poseen unos ficheros de traducciones que son leídos durante el inicio de la aplicación. Estos ficheros contienen claves de traducción, y la traducción asociada a cada clave, para cada idioma. De esta forma, los plugins pueden solicitar la traducción de una clave y Andami devolverá la traducción para el idioma que el usuario haya elegido (a través de la configuración del idioma). Usando este mecanismo se puede presentar al usuario una interfaz totalmente traducida a su idioma preferido.
Los ficheros de traducción deben estar situados en el directorio raíz del plugin, y se deben llamar de acuerdo a lo especificado en la etiqueta resourceBundle del config.xml. Los sufijos de idioma de cada nombre de fichero corresponden con el código de dos caracteres ISO 639-1 (en para inglés, fr para francés, etc). El fichero sin sufijo corresponde al idioma Castellano. Siguiendo estas convenciones, las traducciones del plugin son cargadas automáticamente durante la carga de plugins.
Para acceder a estas traducciones desde el plugin, usaremos un código como el siguiente:
PluginServices ps = PluginServices.getPluginServices(this);
ps.getText("clave_de_traduccion");
o bien, a través de un método estático:
PluginServices.getText(this,"clave_de_traduccion");
Hay que tener en cuenta que todos los plugins tienen un espacio de claves de traducción único. Esto significa que si un plugin A posee una traducción como la siguiente en el fichero text.properties:
error_conexion="Error de conexión"
y un plugin B posee una traducción como:
error_conexion="Error conectando a la base de datos"
entonces habrá un conflicto de claves. Si suponemos que el plugin A se carga antes (y por tanto sus traducciones también se cargan antes), cuando el plugin B intente acceder a la traducción:
PluginServices ps = PluginServices.getPluginServices(this);
ps.getText(error_conexion);
obtendría la traducción errónea: "Error de conexión". Por tanto, debemos intentar utilizar claves que describan con precisión el mensaje, para evitar este de error. Si el plugin no va a ser integrado en la distribución principal de gvSIG, podemos usar un prefijo de clave para curarnos en salud:
org.gvsig.MipluginCasero.error_conexion="Error conectando a la base de datos"
El servicio de registro permite mantener un histórico de sucesos de una sesión completa de gvSIG. Los sucesos pueden tener distinta categoría: Información, Avisos, Errores o Información para Depuración. Al registrar algún mensaje en el log, automáticamente se registra también el plugin que genera el mensaje.
Es importante registrar los errores en el log, a fin de poder depurarlos posteriormente. Es frecuente que los usuarios encuentren errores que somos incapaces de reproducir en nuestra máquina, y la única herramienta con la que podemos corregir estos errores es mediante la información registrada en el log. El fichero de log se almacena en el directorio gvSIG dentro del directorio del usuario. En Windows, típicamente está en:
C:\Archivos de programa\Documents and Settings\%USER%\gvSIG\gvSIG.log
y en Linux, está en:
/home/$USER/gvSIG/gvSIG.log
Utilizar el log desde un plugin es muy sencillo:
Logger logger = PluginServices.getLogger();
logger.info("Mensaje a mostrar en el log");
Como ya hemos dicho, existen 4 niveles de severidad:
- depuración (DEBUG), indica que esta información sólo es interesante para depurar (corregir) el programa. En esta severidad podemos mostrar (por ejemplo) los típicos mensajes que verifican que estamos entrando dentro de un if.
- información (INFO), indica que el mensaje es meramente informativo (no contiene ningún error).
- aviso (WARNING), indica que la información mostrada tiene la intención de avisar de algún hecho especial. Debemos mostrar en esta severidad los hechos inusuales o inesperados que puedan ser indicios de error (pero no errores claros).
- error (ERROR), indica que se ha producido un error en la aplicación. Debemos mostrar información relevante para poder detectar el origen del error y permitir subsanarlo (ya sea al usuario o al programador).
Accederemos a estos cuatro niveles usando los correspondientes métodos: debug(), info(), warning() y error().
Si se ha producido una excepción y deseamos incluir en el log la salida del método printStackTrace, podemos hacerlo de forma muy sencilla:
catch (Exception excepcion) {
logger.error("Mensaje de error", excepcion);
}
Existe un fichero (plugin-persistence.xml) en el que los plugins pueden almacenar información (ejemplos: un plugin de WMS podría almacenar la lista de servidores recientes; un plugin de nomenclátor podría almacenar las últimas búsquedas realizadas). De esta forma, al volver a arrancar gvSIG, los plugins pueden recuperar la información guardada y presentar (por ejemplo) la lista de servidores usados en la última sesión. Cada plugin sólo tiene acceso (de forma directa) a la información que él mismo produce. Debemos aclarar que este fichero es adecuado para guardar pequeñas cantidades de información de texto, pero no es adecuado para guardar otros tipos de información como miniaturas de la cartografía, un listado de nombres de municipios, etc.
El fichero se lee automáticamente durante el inicio de Andami, y se guarda automáticamente al salir de la aplicación. El fichero está en formato XML, que almacena la información con una estructura de árbol. Para poder leer o escribir información en esta estructura, haremos uso del objeto XMLEntity, que representa una subrama del árbol (una subrama puede contener desde un único nodo hasta el árbol completo).
Para obtener la subrama asociada a nuestro plugin, utilizaremos:
XMLEntity entity = PluginServices.getPluginServices(this).getPersistentXML();
Si suponemos que previamente habíamos almacenado una propiedad llamada "last.server", podemos recuperarla usando:
try {
XMLEntity entity = PluginServices.getPluginServices(this).getPersistentXML();
String lastServer = entity.getStringProperty("last.server");
}
catch (NotExistInXMLEntity exception) {
// mostrar el error y tomar alguna acción en respuesta
}
Podemos ver el objeto XMLEntity como una especie de tabla Hash que asocia claves con valores. En el ejemplo anterior, "last.server" es una clave.
Para almacenar una nueva clave:
XMLEntity entity = PluginServices.getPluginServices(this).getPersistentXML();
entity.putProperty("last.server", "http://www.gvsig.org");
Es posible almacenar diversos tipos de objetos en el XMLEntity:
XMLEntity entity = PluginServices.getPluginServices(this).getPersistentXML();
entity.putProperty("last.server", "http://www.gvsig.org");
entity.putProperty("maxConnections", 5);
entity.putProperty("timeout", 3.258);
String[] servers = {"http://www.gvsig.org","www.gvsig.gva.es","www.cit.gva.es"}
entity.putProperty("preferred.servers", servers);
int[] allowedValues = {1,2,5,10};
entity.putProperty("allowed.values", allowedValues);
Y para recuperar estos objetos:
try {
XMLEntity entity = PluginServices.getPluginServices(this).getPersistentXML();
String lastServer = entity.getStringProperty("last.server");
int maxConnections = entity.getProperty("maxConnections");
float timeout = entity.getFloatProperty("timeout");
String[] servers = entity.getStringArrayProperty("preferred.servers");
int[] allowedValues = entity.getIntArrayProperty("allowed.values");
}
catch (NotExistInXMLEntity exception) {
// mostrar el error y tomar alguna acción en respuesta
}
Además, podemos anidar los objetos XMLEntity:
XMLEntity entity = PluginServices.getPluginServices(this).getPersistentXML();
XMLEntity hijo = new XMLEntity();
hijo.setName("ColorData");
hijo.putProperty("red", "#ff0000");
hijo.putProperty("green", "#00ff00");
hijo.putProperty("blue", "#0000ff");
hijo.putProperty("black", "#000000");
hijo.putProperty("white", "#ffffff");
entity.addChild(hijo);
Y para recuperar el XMLEntity anidado:
XMLEntity child=null, tmpchild;
for (int i=entity.getChildrenCount()-1; i>=0; i--) {
tmpchild = entity.getChild(i);
if (child.getName().equals("ColorData")) {
child = tmpchild;
break;
}
}
if (child!=null) {
try {
String rojo = child.getStringProperty("red");
String verde = child.getStringProperty("green");
String azul = child.getStringProperty("blue");
String negro = child.getStringProperty("black");
String blanco = child.getStringProperty("white");
}
catch (NotExistInXMLEntity exception) {
// mostrar el error y tomar alguna acción en respuesta
}
}
Andami facilita la creación de tareas en segundo plano, permitiendo además que estas tareas sean cancelables (interrumpibles). Si deseamos realizar un procesamiento prolongado, conviene lanzarlo en un hilo separado (proceso en segundo plano), para no bloquear la interfaz gráfica. Un típico ejemplo de tarea a lanzar en segundo plano sería la exportación de una capa a un fichero. Se presentará un diálogo al usuario con el progreso de la tarea, y un botón que permita la cancelación del proceso.
Para crear la tarea usaremos el método:
- public static void cancelableBackgroundExecution IMonitorableTask task)
Para crear cómodamente una tarea en segundo plano, existe la clase abstracta AbstractMonitorableTask que implementa IMonitorableTask y que podemos extender anónimamente para crear nuestra tarea:
PluginServices.cancelableBackgroundExecution(new AbstractMonitorableTask() {
public void run() {
for (int i=0; i<100001; i++) {
System.out.println("hilo mundo "+i);
}
}
});
Simplemente con esto, la parte que está dentro del método run() se ejecutará en un nuevo hilo.
Si queremos mostrar un diálogo de progreso y dar la posibilidad de cancelar la operación, podemos añadir algunas líneas:
PluginServices.cancelableBackgroundExecution(new AbstractMonitorableTask() {
public void run() {
setNote(PluginServices.getText(this, "Exportar_fichero"));
setInitialStep(0);
setFinalStep(100000);
for (int i=0; i<100001; i++) {
if (!isCanceled()){
System.out.println("hilo mundo"+i);
setCurrentStep(i);
}
else {
return;
}
}
}
});
El diálogo desaparecerá automáticamente al llegar al final step (en este caso 100.000), y no desaparecerá hasta que llegue al final step aunque el nuevo hilo ya haya finalizado.
Si tenemos alguna necesidad especial que AbstractMonitorableTask no cubre, existen varias opciones:
- Extender AbstractMonitorableTask de forma no anónima, reescribiendo los métodos que necesitemos y el constructor.
- Extender DefaultCancellableMonitorable en una nueva clase que a la vez implemente IMonitorableTask, reescribiendo los métodos que necesitemos y el constructor.
- Crearnos una clase totalmente nueva que simplemente implemente IMonitorableTask e incorpore toda la funcionalidad que necesitemos.
El siguiente diagrama muestra toda la jerarquía de clases implicada en la creación de tareas en segundo plano, para que veamos con más claridad el significado de extender de una u otra clase:
Al extender AbstractMonitorableTask de forma anónima y pedirle que muestre el diálogo de progreso, nos muestra un diálogo de progreso indeterminado, es decir, la barra no avanza desde el inicio hasta el fin, si no que oscila a izquierda y derecha continuamente hasta que acaba la tarea. Ahora vamos a mostrar un ejemplo en el que extenderemos AbstractMonitorableTask de forma no anónima a fin de mostrar una barra de progreso determinada:
public class BackgroundTask extends AbstractMonitorableTask {
public BackgroundTask() {
setDeterminatedProcess(true);
setNote(PluginServices.getText(this, "Exportar_fichero"));
setStatusMessage(PluginServices.getText(this, "Exportando..."));
setInitialStep(0);
setFinalStep(100000);
}
public void run() {
for (int i=0; i<100001; i++) {
if (!isCanceled()){
System.out.println("hilo hilero"+i);
setCurrentStep(i);
}
else {
return;
}
}
}
}
y haremos uso de ella de la siguiente forma:
PluginServices.cancelableBackgroundExecution(new BackgroundTask());
Andami ofrece facilidades para crear ficheros temporales, que son eliminados automáticamente al salir de gvSIG (siempre que gvSIG finalice correctamente). Por una parte, si suministramos un nombre y unos datos, Andami creará el fichero temporal con el contenido proporcionado. Por otra parte, podemos solicitar la ruta del directorio de ficheros temporales y crear en él los ficheros que necesitemos. En ambos casos, los ficheros se eliminan automáticamente al salir de gvSIG. Para acceder a este servicio no usamos PluginServices sino una clase llamada Utilities.
public static void createTemp(String fileName, String data)throws IOException
Crea un fichero llamado fileName, escribe en él el contenido de data y finalmente cierra el fichero. Al salir de gvSIG, el fichero se cerrará automáticamente.
public static String createTempDirectory()
Crea el directorio para ficheros temporales, si no existe, y devuelve la ruta al mismo. Todos los ficheros que creemos en este directorio se borrarán automáticamente al salir de gvSIG.
Es frecuente que los plugins incluyan diversos recursos (normalmente imágenes, tal vez algún fichero XML, cartografía en algún plugin especial...). A la hora de acceder a ellos, no debemos usar rutas absolutas ni tampoco rutas relativas que incluyan el nombre del plugin, ya que eso es poco portable (un cambio en el nombre del plugin provocaría que el código dejase de funcionar). Lo correcto es acceder a ellos usando una ruta relativa al directorio raíz del plugin.
Cada plugin tiene asociado un PluginClassLoader que es capaz de cargar recursos usando rutas relativas al directorio raíz del plugin. Para acceder a dicho ClassLoader, tenemos varias opciones:
PluginServices ps = PluginServices.getPluginServices(this);
PluginClassLoader loader = ps.getClassLoader();
o bien
ClassLoader loader = this.getClass().getClassLoader();
Una vez tengamos el ClassLoader, usaremos el método getResource() para obtener una URL con la ruta absoluta del recurso. Por ejemplo, supongamos que queremos cargar una imagen llamada close.png que está en el subdirectorio images de nuestro plugin. Es decir, la imagen está en gvSIG/extensiones/org.gvsig.miplugin/images/close.png y por tanto la ruta relativa al directorio raíz del plugin sería images/close.png. Para acceder a ella usaríamos:
URL imageURL = loader.getResource("images/close.png");
if (imageURL!=null)
ImageIcon icon = new ImageIcon(imageURL);
Andami ofrece métodos para obtener texto del portapapeles, o depositar texto en él:
public static void putInClipboard(String data)
Deposita en el portapapeles la cadena de texto contenida en data.
public static String getFromClipboard()
Obtiene el contenido de texto del portapapeles si existe alguno, o null en caso contrario.
Estos métodos están pensados para agilizar el acceso cuando requiramos manipulaciones simples del portapapeles, y consituyen simples atajos para los métodos estándar de Java . Si necesitamos manipular otros tipos de datos (o hacer uso de características avanzadas como la selección de sistema), debemos echar un vistazo a las clases ClipBoard, Transferable y DataFlavour de Java y a los métodos Toolkit.getDefaultToolkit().getSystemClipboard() y Toolkit.getDefaultToolkit().getSystemSelection().
Introducción
MapOverview es un componente Java de GUI de tipo MapControl, que actúa como localizador de un objeto MapControl (llamémoslo mc_as_obj).
Ambos contienen la información en la misma proyección.
El extent ajustado de mc_as_obj será dibujado como un rectángulo con el borde rojo, y semitransparente en MapOverview, utilizando para ello un doble-buffer.
MapOverview simula una sola herramienta, denominada "zoomtopoint", con la que el usuario podrá seleccionar de distintos modos un área rectangular que servirá de extent al puerto de vista de mc_as_obj, (el área final seleccionada será la dicho extent, ajustado).
El comportamiento simulado por MapOverview es una composición de 4 Behavior, en combinación con 3 ToolListener, más las operaciones zoom in y zoom out que simula MapControl :
Behavior |
ToolListener |
Herramienta simulada |
PointBehavior |
MapOverviewListener |
Centra el adjusted extent en el punto seleccionado con el 2º o 3º botón del ratón. |
RectangleBehavior |
MapOverviewChangeZoomListener |
Realiza un zoom ajustando el extent al área rectangular seleccionada con 1º botón
del ratón. |
DraggerBehavior |
MapOverviewChangeZoomListener |
Dibuja el área rectangular seleccionada según se arrastra el 1º botón del ratón. |
DraggerBehavior |
MapOverviewPanListener |
Permite arrastrar el área rectangular seleccionada con el 3º botón del ratón. |
|
|
Aparte soporta las herramientas ofrecidas por MapControl nativamente, moviendo la
rueda del ratón.
- Zoom In centrado en el punto que indica el cursor.
- Zoom Out centrado en el punto que indica el cursor.
|
Utiliza como cursor el del MapOverviewListener
Diagrama
La figura 1 muestra el diagrama de clases que resume la implementación de MapOverview según se ha explicado:
libFMap es un proyecto software desarrollado en Java como parte del Proyecto gvSIG, con la finalidad de disponer de una librería que permita trabajar con información geográfica, cartográfica, topográfica, ... según estándares internacionales, y a la vez con imágenes, en forma de capas gráficas, tipo raster o vectorial.
Para poder trabajar con dicha información, incluye drivers para el acceso a los múltiples formatos, y ofrece un conjunto de herramientas para trabajar con las capas visuales: zoom in, zoom out, pan (para arrastrar usando el ratón), selección de figuras geométricas, etc.
Soporta edición de capas (aquellas que se puedan editar), con un conjunto propio de herramientas. Trabaja también con índices espaciales, para acelerar el acceso a los datos (esto es recomendable cuando hay muchos en las capas, o incluso están en repartidos en múltiples niveles).
Existen, pues, una amplia variedad de tipos de capas, cada una con su propia estrategia a la hora de dibujarse y recorrer su información.
libFMap agrupa las capas en forma de árbol, de manera que se amplía el horizonte de trabajo, pudiendo servir tanto para obras civiles, medio ambiente, arquitectura, arqueología, etc.
- FLayers: Información sobre las capas: tipos, estatus, funcionalidad, ...
- MapControl: Componente gráfico central de libFMap, incorpora las capas, soporte para su dibujado, su comportamiento, herramientas para trabajar con ellas, ...
- gvSIG: Toda la información pública del Proyecto gvSIG.
Los distintos modos en que un usuario puede interactuar con un objeto de tipo MapControl son lo que se denomina behavior.
Así, en un momento dado, puede ser necesario tener que trabajar seleccionando una polilínea, o un punto, o un rectángulo, o arrastrando el ratón sobre un MapControl, o, porqué no, combinando varios de estos comportamientos individuales (por ej. seleccionando un punto del objeto de tipo MapControl y arrastrando (drag & drop) el ratón) ... Cada uno de estos comportamientos posibles con los que trabajar con MapControl es lo que, en la terminología de libFMap se denomina behavior. De hecho, es común utilizar varios de estos comportamientos básicos combinados para definir la interacción con MapControl vía una herramienta determinada.
Ninguno de los behavior se encargará de tratar la información seleccionada por el usuario sobre MapControl, para esto están las ToolListeners. Behavior se encarga de procesar los eventos que se producen en MapControl, preparando eventos genéricos que enviará a la ToolListener actual, con la información necesaria.
De este modo, se consigue simular distintas herramientas con las que un usuario podrá trabajar con un objeto de tipo MapControl, y a la vez facilitar al desarrollador el incorporar nuevas herramientas, basándonos en el principio de divide y vencerás y una variante del patrón software "Chain-of-responsibility pattern".
Actualmente, todos los eventos que puede recibir cualquier tipo de behavior son producidos por el manejo del ratón sobre el objeto MapControl, y estos pueden ser de tipo MouseEvent, o de tipo MouseWheelEvent, según su naturaleza, atenderá a uno u otro tipo de evento. Por otro lado, cualquiera de las excepciones que puede lanzar son de tipo BehaviorException.
El diagrama 1 muestra cómo se integra en MapControl, y la relación con los tipos de eventos que puede recibir, o excepciones que puede lanzar.
Tipos de behavior en libFMap
El diagrama 2 muestra los tipos de comportamientos básicos implementados en la librería libFMap.
Tipo |
Proyecto |
Modo de interacción |
Eventos hacia
la herramienta |
Herramienta
asociada |
CADToolAdapter |
extCAD |
Por Ratón:
Haciendo click con el botón derecho del ratón solicita a la herramienta
actual que muestre un popup con las opciones de edición actualmente
disponibles. Si es con el botón izquierdo agrega un punto en
dicha posición. Soltando cualquier botón solicita a MapControl que se
repinte. Mientras que, moviéndolo además actualiza las coordenadas en la
barra de estado. En caso que esté habilitado el snap, actualizará el
icono del cursor conforme el punto de control más cercano en una pequeña
área de tolerancia bajo.
Por Teclado:
En caso de escribir un comando por teclado, cambia a una herramienta en
modo selección, y según el tipo de comando escrito y el estado actual de
la herramienta, puede tomar la información como un nuevo punto, una opción
o el valor de una opción. Aplicará cambios, o notificará que el
comando no es válido, en la misma consola de edición.
|
MouseEvent,
InputEvent |
1 o más
CADTool |
CircleBehavior |
libFMap |
Pulsando el botón izquierdo del ratón, permite definir un área circular:
primero se indica centro, y luego un punto de la circunferencia, mediante
el botón izquierdo del ratón. |
MeasureEvent |
CircleListener |
CompoundBehavior |
libFMap |
Funciona como una combinación de Behavior de libFMap. El modo de
interacción dependerá de los Behavior que lo componen. |
MouseEvent,
MouseWheelEvent |
1 o más
ToolListener |
DraggerBehavior |
libFMap |
Arrastrando el ratón y manteniendo pulsado cualquiera de sus botones. Irá
actualizando MapControl teniendo en cuenta la variación del punto actual
respecto al inicial. Una vez soltado el ratón, el cambio será permanente. |
MoveEvent |
PanListener |
MouseMovementBehavior |
libFMap |
Notifica eventos de movimiento, o arrastre (mantener pulsado un botón del
ratón y a la vez moverlo) del ratón sobre MapControl. |
PointEvent |
PointListener |
MoveBehavior |
libFMap |
Igual que DraggedBehavior pero manteniendo pulsado el botón izquierdo
del ratón. |
MoveEvent |
PanListener |
PointBehavior |
libFMap |
Selección de un punto con un click por medio de cualquier botón del ratón.
Pulsando una vez, solicita la cancelación del dibujado actual de
MapControl.
Siempre notifica el que un botón haya dejado de estar pulsado, y en caso
que se hubiese hecho un click doble, notifica el evento al soltar el
botón.
|
PointEvent |
PointListener |
PolylineBehavior |
libFMap |
Con un click de cualquier botón del ratón, notifica y almacena el punto,
iniciándose así el modo de dibujado de polilínea. Todos los eventos de
movimiento o arrastre del ratón posteriores son tambien notificados,
hasta que con 2 clicks se finaliza el modo, enviando todos los vértices
seleccionados por el usuario a la ToolListener asociada. |
MeasureEvent |
PolylineListener |
PolygonBehavior |
libFMap |
Igual a PolylineBehavior, pero ahora el primer punto será también el
último de la polilínea, y cada nuevo en agregarse será el penúltimo. |
MeasureEvent |
PolylineListener |
RectangleBehavior |
libFMap |
Notifica el punto donde se hizo click con el botón izquierdo del ratón,
a partir de ese momento según se arrastre el ratón se mostrará un
rectángulo tomando como segundo vértice en diagonal la posición actual del
cursor. Una vez se suelte el botón, quedará definida el área rectangular.
Así pues, durante el arrastre y al soltar el botón, se generarán eventos
que notifiquen la posición actual del ratón.
Via los puntos inicial y el final se definirá un rectángulo con los lados
paralelos a los ejes X e Y de coordenadas.
|
RectangleEvent |
RectangleListener |
Chain of responsibility Pattern: información acerca de este patrón software, con código fuente de ejemplo.
ToolListeners: herramientas que combinadas con algún comportamiento Behavior permiten trabajar con MapControl.
ToolListener events: eventos que utiliza un Behavior para notificar información a su/s ToolListener asociada.
Según el comportamiento que se quiere simular en MapControl, los eventos de ratón que se produzcan en este, serán base para la creacción de algún tipo de evento que complete la información necesaria para que la ToolListener asociada a dicho comportamiento pueda completar la simulación de la herramienta con la que se interactúa con MapControl.
Existen cuatro tipos de estos eventos, todos ellos producidos como consecuencia de eventos de ratón (MouseEvent) al trabajar el usuario sobre dicha vista, que son los únicos que puede recibir cualquiera de los tool listeners.
Así, una herramienta como el pan, que requiere arrastrar con el ratón, necesitará recibir no sólo la información del evento de ratón nativo de Java (MouseEvent), sino también las posiciones inicial y final de dicho arrastre.
Si en cambio se está seleccionando una herramienta que solo requiere información de un punto dado, necesitaré dicho punto. O si es por área, información de dicha área.
Se buscaba tener el mínimo número de eventos posibles, y que sean lo más genéricos para que sirvan para el máximo número de tipos de tool listeners, y así facilitar la programación. Esto hizo que se crearan los 4 tipos de eventos siguientes, que se crearán como consecuencia de alguna acción del ratón sobre MapControl y cuyo evento (MouseEvent) almacenarán internamente. Decir, por último que ninguno de estos eventos se considera de tipo FMapEvent, dado que estos últimos están más relacionados con el dibujado de las capas.
MeasureEvent: evento asociado a tool listeners que permiten crear o seleccionar polilíneas abiertas, cerradas, que se corten o no.
Información adicional que aporta:
- Vector con la coordenada X de todos los puntos seleccionados.
- Vector con la coordenada Y de todos los puntos seleccionados.
- Un objeto con toda la geometría dividida en líneas rectas y curvas de Bezier cuadráticas o cúbicas.
- Evento de ratón producido en MapControl, que fue la causa de que Behavior generase este evento.
MoveEvent: evento asociado a tool listeners que requieren que el usuario realice un movimiento del ratón sobre pantalla, tipo drag & drop (arrastrar y soltar).
Información adicional que aporta:
- Punto inicial del movimiento de ratón.
- Punto final del movimiento de ratón.
- Evento de ratón producido en MapControl, que fue la causa de que Behavior generase este evento.
PointEvent: evento asociado a tool listeners que requieren que el usuario seleccione un punto.
Información adicional que aporta:
- Punto donde se generó el evento.
- Evento de ratón producido en MapControl, que fue la causa de que Behavior generase este evento.
RectangleEvent: evento asociado a tool listeners que requieren que el usuario seleccione un área rectangular.
Información adicional que aporta:
- Rectangulo seleccionado en coordenadas del mundo.
- Rectangulo seleccionado en coordenadas de pantalla (píxels).
- Evento de ratón producido en MapControl, que fue la causa de que Behavior generase este evento.
MapControl es un componente Java de intefaz gráfica, que pinta un conjunto de capas con información gráfica, vía su objeto MapContext que las contiene, y captura los eventos de ratón que se producen en él vía un Behavior (que puede ser una composición de varios) que define cómo se comporta actualmente, enriqueciendo dichos eventos con la información necesaria para poder simular la herramienta de interacción actual, vía ToolListener, que será la que complete la simulación.
Según el tipo de capas visibles y activas, gvSIG nos proporcionará una serie de herramientas con las que el usuario podrá interaccionar con el objeto MapControl que contiene las capas.
Al seleccionar cualquiera de las herramientas disponibles, lo que se está haciendo es seleccionar un comportamiento para trabajar con MapControl, que puede ser el resultado de múltiples comportamientos denominados cada uno Behavior, cada uno con una ToolListener.
La librería libFMap define cada uno de estos comportientos básicos, que procesan los eventos de ratón producidos en MapControl, generando otros eventos con la información necesaria según su naturaleza. Estos eventos serán los que se envíen a la herramienta actualmente seleccionada para interactuar con MapControl.
Dicha herramienta tendrá un icono que verá el usuario en su cursor, y una serie de métodos que serán invocados según el tipo de evento que se produzca.
Las ToolListener incluyen la lógica que complementa a los Behavior para simular una herramienta con la que interactuar con un objeto de tipo MapControl.
En la librería libFMap se definen las ToolListener básicas, pero existen otras muchas que heredan de estas, y están definidas en otros proyectos.
MapControl utiliza un doble-buffer propio para el dibujado, intentando en la medida que sea posible, dibujar en el buffer, y una vez finalizado, enviar esa información a pantalla.
MapControl crea un objeto compartido de tipo Cancellable, con el que notificará a MapContext y a las capas que se estén dibujando el que pueden seguir con el proceso, o deben cancelar el dibujado. (No todas las capas pueden cancelar su dibujado).
Normalmente, cuando se utiliza una instacia de MapControl, se asignan sus posibles comportamientos, identificándolos cada uno con una cadena de texto. Posteriormente, según la herramienta con la que el usuario esté trabajando, se le indicará al objeto MapControl, que utilice como "herramienta activa" uno de ellos.
Se busca siempre tener el menor tiempo de respuesta en la interacción con el usuario, por ello en caso que el proceso de pintado sea pesado, se actualiza la pantalla con el valor de pintado del buffer cada t milisegundos.
MapControl se encarga de atender sus peticiones de pintado mediante un objeto de tipo Drawer2 que notificará a su hilo trabajador para que ejecute solo una a la vez, manteniendo otra en espera. Si llegase una petición de pintado, habiendo otra en espera, esta segunda se perdería. Por otro lado, si el hilo "trabajador" encargado de pintar, finalizase, quedaría en espera pasiva, hasta nueva notificación por parte de Drawer2.
Para indicar qué es lo que se repintará, MapControl define 3 estados posibles:
- ACTUALIZADO / UPDATED : refresca la pantalla con el contenido del buffer. Es proceso de pintado rápido.
- DESACTUALIZADO / OUTDATED : actualizar toda la información visual de todas las capas visibles y disponibles. Puede ser el proceso más pesado de pintado.
- ONLY_GRAPHICS : actualizar capa/s de simbología y geometrías.
Proceso de pintado
El proceso de pintado de MapControl sigue el siguiente algoritmo:
- Si el status es ACTUALIZADO:
- Si hay doble-buffer:
- Si hay un Behavior para gestionar la instancia de MapControl: delega el proceso de dibujado a dicho Behavior, invocando: behavior_instance.paintComponent(g).
- Sino, repinta rápidamente la información gráfica actual, invocando: g.drawImage(image,0,0,null).
- Si el estado es OUTDATED o ONLY_GRAPHICS:
- Repinta rápidamente la información previa invocando g.drawImage(image,0,0,null), y crea una petición de pintado (PaintingRequest), con la que delegará el proceso pesado de pintado al hilo trabajador de Drawer2, siguiendo el patrón software SingleWorketThread, e iniciando el temporizador con el que actualizará la pantalla cada 360 ms. según se ejecuta el proceso pesado de pintado. Una vez es atendida la petición de dibujado, se invoca a MapContext para que pinte las capas: mapContext.draw(image, g, cancel,mapContext.getScaleView()).
Notas sobre el proceso de pintado:
- Puede ser cancelado en cualquier momento invocando cancelDrawing().
- Será en última instancia la implementación de pintado de cada capa la que la pinte, y realice la cancelación, pudiendo haber quienes no cancelen su proceso de pintado.
- Se puede forzar a pintar todas las capas de MapControl invocando: drawMap(boolean).
- Se puede forzar a repintar solo las capas sucias invocando: rePaintDirtyLayers().
- Se puede forzar a repintar solo la capa GraphicLayer invocando: drawGraphics().
Algoritmo de Pintado
- MapControl al iniciarse crea un objeto Drawer2. Este a su vez crea un hilo de tipo Worker.
- Cada vez que se invoca a paintComponent(Graphics g) de MapControl, y su estatus es ACTUALIZADO:
- Si hay doble-buffer:
- Si hay una herramienta activa: le delega el control para que pinte sobre el front-buffer.
- Sino, refresca el front-buffer.
- Cada vez que se invoca a paintComponent(Graphics g) de MapControl, y su estatus es DESACTUALIZADO, o ONLY_GRAPHICS, creará una nueva petición de tipo PaintingRequest que se la asignará al objeto de tipo Drawer2, e iniciará el temporizador. Drawer2 almacenará la petición como waitingRequest, y, en caso que el hilo trabajador estuviese esperando, le notifica que vuelva a ejecución.
- El hilo trabajador de Drawer2, (Worker), una vez en ejecución, intenta recoger una petición (PaintingRequest).
- Si no hubiese ninguna, Drawer2 lo pondría en espera pasiva.
- En caso que hubiese alguna, Drawer2 le asignaría waitingRequest, que pasaría a ser ahora paintingRequest.
- Si estando el hilo trabajador en ejecución, llegase otra petición, pasaría a ser waitingRequest.
- El hilo Worker:
- Si MapControl tiene double-buffer donde pintar:
- Avisa a MapControl para que cancele cualquier proceso de dibujado previo, invocando cancelDrawing().
- Ejecuta el algoritmo de pintado de la petición: painting_request_instance.paint():
- Fuerza la cancelación de procesos de dibujado previos, vía el objeto compartido de tipo CancelDraw.
- Si status es DESACTUALIZADO:
- Obtiene el buffer sobre el que pintar: el back-buffer del double-buffer de MapControl: Graphics2D g.
- Actualiza el back-buffer con el color de fondo del ViewPort, o en blanco, si no hubiese ninguno definido.
- Pone status = ACTUALIZADO.
- Avisa a MapContext para que pinte las capas: mapContext.draw(double-buffer, front-buffer, canceldraw_compartido, escala_del_ViewPort).
- Si status es ONLY_GRAPHICS:
- Pone status = ACTUALIZADO.
- Avisa a MapContext para que pinte GraphicLayer: mapContext.drawGraphics(double-buffer, front-buffer, canceldraw_compartido, escala_del_ViewPort).
- Para el temporizador.
- Repinta MapControl.
- Sino, pondrá: status = DESACTUALIZADO
- Por su parte, cada vez que se dispare el temporizador, mandará refrescar MapControl, de modo que el usuario verá la imagen parcialmente, cada vez más completa.
- Si se produce alguna excepción ejecutando el código de la petición de pintado (PaintingRequest), parará el temporizador, y la notificará a ExceptionHandlingSupport, para que la notifique a todos los ExceptionListener registrados.
MapControl permite registrar listeners, de tipo ExceptionListener, con los que recibir notificación de expcepciones producidas:
- Atendiendo una petición de pintado.
- Trabajando con la herramienta activa.
- O, ejecutando alguna de las operaciones soportadas por defecto por MapControl: zoom in, o zoom out .
El diagrama 1 muestra los principales elementos que intervienen con MapControl.
Pulse aquí para ver el diagrama en grande.
Descripción del diagrama
- MapControl es un componente Swing que implementa:
- ComponentListener para atender eventos producidos al moverlo, ocultarlo, redimensionarlo, o mostrarlo. En realidad estos métodos no están implementados, sino que se han dejado por si alguna subclase los necesitase.
- CommandListener para atender eventos producidos por la ejecución de operaciones de edición de capas.
- Contiene un doble-buffer propio sobre el que realizar el proceso de pintado: se dibuja en el back-buffer, y una vez completado, o cada cierto tiempo si el proceso es largo, se actualiza el front-buffer con el back-buffer.
- Crea un objeto (CancelDraw) compartido de tipo Cancellable con el que podrá notificar a todas las capas y MapContext si pueden continuar el proceso de dibujado, o deben cancelarlo.
- Las distintas capas con información gráfica las almacena en un objeto de tipo MapContext, que servirá para gestionarlas, gestionar los eventos (agrupados en AtomicEvent) que se producen en ellas mediante un buffer (EventBuffer), proyectarlas según una proyección y el puerto de vista (ViewPort) disponible, y controlar su dibujado: calidad, ...
- Contiene una serie de identificadores que definen su posible comportamiento, cada identificador asociado a un Behavior o una composición de estos. En un momento dado, tendrá como currentMapTool, sólo uno de estos comportamientos posibles.
- Define un listener: MapToolListener, que será el encargado de procesar cualquier evento de ratón que se produzca en MapControl, y notificarlo a la herramienta activa: currentMapTool. Para el caso del evento por rueda del ratón: MouseWheelEvent, solo notifica un evento por segundo, para dar tiempo a que se pueda cancelar el proceso anterior.
- La herramienta activa delega el control a la ToolListener asociada, enviándole un evento con la información necesaria para que realice dicho procesamiento. Cualquier excepción durante dicho procesamiento implicará el lanzamiento de alguna BehaviorException.
- BehaviorException es una simple excepción de Java, que sirve para identificar un problema producido en la ejecución del código de un Behavior.
- Toda excepción producida atendiendo una petición de pintado, trabajando con la herramienta activa, o, ejecutando alguna de las operaciones soportadas por defecto por MapControl: zoom in, o zoom out, será lanzada por un objeto de tipo ExceptionHandlerSupport a las ExceptionListener que estuviesen registradas.
- Todos los eventos (de tipo AtomicEvent) que pueda lanzar el objeto interno de tipo MapContext serán capturados y procesados por un listener de tipo MapContextListener definido en MapControl.
- Siempre que es invocado el método protected void paintComponent(Graphics g) de MapControl, y su estatus es DESACTUALIZADO, o ONLY_GRAPHICS, se creará una nueva petición de pintado: PaintingRequest, que se le notificará el Drawer2, objeto encargado de gestionarlas.
- El objeto interno de tipo Drawer2 posee un hilo trabajador (Worker) que es el que realiza el pintado implementado en la petición (PaintingRequest). Drawer2 sólo almacena 2 peticiones de pintado, la que está siendo atendida, y una en cola:
- Si llega una nueva petición, se borra la que estaba en cola.
- Si el hilo trabador acaba, intenta obtener la petición en cola, en caso que no hubiese, pasa a espera pasiva.
- Estando el hilo trabajador en espera pasiva, si llega una petición de pintado, se notifica al hilo para que vuelva a ejecución.
- Contiene un temporizador (Timer) que utilizará para refrescar el front-buffer del double-buffer cada 360 ms. con la información del back-buffer, mientras se está ejecutando un proceso pesado de pintado.
- Cancelación del pintado: permite cancelar el proceso actual de pintado en cualquier momento.
- Componente Java de GUI: el desarrollador puede incorporar MapControl en la interfaz gráfica de su aplicación como otro JComponent más.
- Doble buffer: obtener el doble-buffer con el que pinta las capas.
- Herramientas:
- Agregar un Behavior, o un conjunto (que será un CompoundBehavior), identificado por un String, como herramienta con las que se podría interactuar con el objeto MapControl.
- Buscar una herramienta registrada, vía su identificador.
- Obtener todas las herramientas registradas, junto con su identificador.
- Obtener sólo los identificadores de las herramientas registradas.
- Establacer una de las herramientas registradas, como activa, de modo que definirá el comportamiento de MapControl.
- Obtener la herramienta activa, o su identificador.
- Averiguar si hay una determinada herramienta registrada.
- Establecer como activa, la que previamente lo estuvo.
- Listeners:
- ExceptionListener : agregar, o eliminar este tipo de listener, o notificar a todos estos listeners registrados de alguna excepción producida:
- Atendiendo una petición de pintado.
- Trabajando con la herramienta activa.
- O, ejecutando alguna de las operaciones soportadas por defecto por MapControl: zoom in, o zoom out .
- MapContext: establecer u obtener un objeto MapContext. Las distintas capas con información gráfica las almacena en un objeto de tipo MapContext, que servirá para gestionarlas, gestionar los eventos (agrupados en AtomicEvent) que se producen en ellas mediante un buffer (EventBuffer), proyectarlas según una proyección y el puerto de vista (ViewPort) disponible, y controlar su dibujado: calidad, ...
- Pintado: ofrece distintos mecanismos de pintado ( vea `Pintado de MapControl`_ ):
- Forzar pintado completo de todas las capas (cancelando el pintado previo que estuviese en marcha):
- Pintado completo de todas las capas: drawMap(false).
- Pintado completo de todas las capas, vaciando el front-buffer con el color de fondo del puerto de vista, o a blanco, si no estuviese definido: drawMap(true).
- Pintar sólo la capa de tipo GraphicLayer existente: drawGraphics().
- Repintar solo las capas que no estén actualizadas (capas sucias) (cancelando el pintado previo que estuviese en marcha): rePaintDirtyLayers().
- Proyección: obtener la proyección actual en que se está mostrando la información gráfica de las capas.
- Puerto de vista: obtener el puerto de vista utilizado para ajustar el extent seleccionado de las capas, al disponible.
- Zoom: MapControl implementa por defecto las operaciones de zoom in, y zoom out.
- Behavior: descripción de los comportamientos básicos.
- Eventos: tipos de eventos que pueden recibir las ToolListener.
- FLayers: las distintas capas con información gráfica que puede contener MapContext.
- MapContext: lógica de gestión y pintado de las capas.
- MapOverview : MapControl que se puede utilizar como vista en miniatura de otro.
- ToolListener: listeners que completan la simulación de una herramienta con la que interactuar con MapControl.
Según el tipo de capas visibles y activas, gvSIG nos proporcionará una serie de herramientas con las que el usuario podrá interaccionar con el objeto MapControl que contiene las capas.
Al seleccionar cualquiera de las herramientas disponibles, lo que se está haciendo es seleccionar un comportamiento para trabajar con MapControl, que puede ser el resultado de múltiples comportamientos denominados cada uno Behavior, cada uno con una ToolListener.
La librería libFMap define cada uno de estos comportientos básicos, que procesan los eventos de ratón producidos en MapControl, generando otros eventos con la información necesaria según su naturaleza. Estos eventos serán los que se envíen a la herramienta actualmente seleccionada para interactuar con MapControl.
Dicha herramienta tendrá un icono que verá el usuario en su cursor, y una serie de métodos que serán invocados según el tipo de evento que se produzca.
Las ToolListener incluyen la lógica que complementa a los Behavior para simular una herramienta con la que interactuar con un objeto de tipo MapControl.
En la librería libFMap se definen las ToolListener básicas, pero existen otras muchas que heredan de estas, y están definidas en otros proyectos. Destacar las que proporciona el proyecto appgvSIG, que, en general, solo agregan como funcionalidad el actualizar las herramientas disponibles en la GUI de gvSIG , una ejecutada la lógica de la herramienta de la que hereda (en libFMap).
Existe multitud de herramientas definidas en la librería libFMap, todas implementan la interfaz ToolListener, y se agrupan en 5 tipos, según se puede observar en el diagrama 1: selección por área circular, rectangular, polinínea, por punto, o de tipo drag & drop (arrastrar y soltar).
Todas reciben eventos fruto de acciones realizadas con el ratón sobre la vista (MapControl) .
Diagrama con herramientas que implementan CircleListener
El diagrama 2 muestra los tool listener definidos en la librería libFMap
asociados a una selección por área circular.
Tipo |
Proyecto |
Descripción |
Cancelable |
Eventos a los que responde |
Icono |
CircleMeasureListener |
appgvSIG |
Calcula el radio del círculo y lo muestra en el la barra de estado de
gvSIG como distancias parcial y total. |
No. |
MeasureEvent |
|
Diagrama con herramientas que implementan PanListener
El diagrama 3 muestra los tool listener definidos en la librería libFMap
asociados a una selección de tipo drag & drop (arrastrar y soltar).
Tipo |
Proyecto |
Descripción |
Cancelable |
Eventos a los que responde |
Icono |
MapOverviewChangeZoomListener |
appgvSIG |
Actualiza el extent del ViewPort del MapControl con el área
rectangular seleccionada, o dibuja en el MapOverview asociado
un rectángulo delimitando dicha área.
De este segundo modo establece dicha área como el extent a visualizar en
el objeto MapControl asociado al MapOverview.
|
Sí. |
RectangleEvent, MoveEvent |
|
MapOverviewPanListener |
appgvSIG |
Desplaza el área rectangular bajo el cursor del ratón en el objeto
MapOverview según se arrastra éste mientras se mantiene pulsado su
botón derecho.
De este modo se establece dicha área como el extent a visualizar en
el objeto MapControl asociado al MapOverview.
|
Sí. |
MoveEvent |
|
PanListenerImpl |
libFMap |
Actualiza el ViewPort del objeto MapControl asociado con un nuevo
extent. |
Sí. |
MoveEvent |
|
PanListener |
appgvSIG |
Igual a PanListenerImpl, actualizando los controles disponibles de la
interfaz gráfica de gvSIG para trabajar con la información actual en el
objeto MapControl asociado. |
Sí. |
MoveEvent |
|
Diagrama con herramientas que implementan PointListener
El diagrama 4 muestra los tool listener definidos en la librería libFMap asociados a una selección por punto.
Pulse aquí para ver el diagrama 4 en tamaño completo.
Tipo |
Proyecto |
Descripción |
Cancelable |
Eventos a los que responde |
Icono |
DwgEntityListener |
extDWG |
Selecciona una geometría que tenga un punto en la posición
seleccionada por el cursor, en una capa con información DWG. |
No. |
PointEvent |
Cursor.CROSSHAIR_CURSOR |
InfoListener |
appgvSIG |
Muestra la información alfanumérica disponible para el punto seleccionado
y un área alrededor de 7 unidades equivalentes en coordenadas del mapa, en
todas las capas activas del MapControl asociado.
Dicha información se muestra en forma de tabla.
|
No. |
PointEvent |
|
LinkListener |
appgvSIG |
Muestra en un panel información (imagen, texto, etc) asociada a cualquier
feature de las capas activas cuya área intersecte con el punto
seleccionado. |
No. |
PointEvent |
|
MapOverviewListener |
appgvSIG |
Crea un rectángulo centrado en el punto seleccionado con el botón
izquierdo del ratón sobre el objeto MapOverview asociado.
De este modo se establece dicha área como el extent a visualizar en
el objeto MapControl asociado al MapOverview.
|
Sí. |
PointEvent |
|
PointSelectionListener |
libFMap |
Selecciona todas las features de capas vectoriales activas del objeto
MapControl asociado cuya área intersecte con el punto seleccionado por
un simple click de cualquier botón del ratón. |
No. |
PointEvent |
|
PointSelectListener |
appgvSIG |
Igual a PointSelectionListener actualizando los controles disponibles de
la interfaz gráfica de gvSIG para trabajar con la información actual en
el objeto MapControl asociado. |
No. |
PointEvent |
|
SelectImageListenerImpl |
libFMap |
ToolListener anticuado que permitía tener SelectImageListener en
appgvSIG para la selección de capa raster. |
Sí. |
PointEvent |
|
SelectImageListener |
appgvSIG |
Selecciona la capa raster situada más arriba en el TOC con información
en la posición indicada en el objeto MapControl asociado. Dicha capa
pasará a estar activa, mientras que el resto estará como no activas.
Posteriormente actualiza los controles disponibles de
la interfaz gráfica de gvSIG para trabajar con la información actual en
el objeto MapControl asociado.
Esta ToolListener también está anticuada.
|
Sí. |
PointEvent |
|
StatusBarListener |
appgvSIG |
Muestra en la barra de estado de la interfaz gráfica de gvSIG las
coordenadas equivalentes al punto sobre el objeto MapControl asociado,
llamémosle m.
Sigue las siguientes reglas para obtener las expresiones de las
coordenadas:
Si m no está proyectado, expresa las coordenadas como distancia en
píxeles respecto la esquina superior izquierda (0, 0).
Si estando proyectado, las unidades de medida de distancia del ViewPort
de m están en grados, calcula las coordenadas geográficas.
En cualquier otro caso expresa las coordenadas como números decimales
teniendo en cuenta la proyección actual.
En caso de no estar proyectado m, expresará las coordenadas como
latitud: Lat = y longitud: Long =, sino como X: X = e Y: Y =, como
prefijo, mientras que como sufijo:
En caso de utilizar números decimales: 8 decimales si la proyección de
*m es EPSG: 4230 ó EPSG: 4326, o 2 decimales con cualquier otra
proyección.
En caso de no utilizar números decimales, seguirá este patrón:
S?Gº M' S'', con:
- S?: opcionalmente, el símbolo - en caso de ser negativa.
- G: grados equivalentes.
- M: minutos equivalentes.
- S: segundos equivalentes.
|
No. |
PointEvent |
No utiliza icono. |
ToolSelectGraphic |
extCAD |
Selecciona ítems de la capa GraphicLayer de objeto MapControl
asociado, cuya área intersecta con el punto indicado. |
No. |
PointEvent |
|
WCSZoomPixelCursorListener |
extWCS |
Realiza una operación de zoom acercar en el objeto MapControl
asociado, tomando como centro el punto seleccionado.
El factor de zoom dependerá de la resolución máxima de las capas activas
con información WCS
|
No. |
PointEvent |
|
ZoomOutListenerImpl |
libFMap |
Realiza una operación de zoom alejar sobre el objeto MapControl
asociado. Para ello calcula el nuevo extent de su ViewPort según
las siguientes ecuaciones:
ViewPort vp = mapControl.getMapContext().getViewPort();
Point2D p2 = vp.toMapPoint(event.getPoint());
double factor = 1/MapContext.ZOOMOUTFACTOR;
Rectangle2D.Double r = new Rectangle2D.Double();
double nuevoX = p2.getX() - ((vp.getExtent().getWidth() * factor) / 2.0);
double nuevoY = p2.getY() - ((vp.getExtent().getHeight() * factor) / 2.0);
r.x = nuevoX;
r.y = nuevoY;
r.width = vp.getExtent().getWidth() * factor;
r.height = vp.getExtent().getHeight() * factor;
vp.setExtent(r);
Hay que contar que el extent calculado no tendrá porqué coincidir con
el que en última instancia se visualize, dado que ViewPort
se encargará de calcular el extent ajustado a partir de éste.
|
Sí. |
PointEvent |
|
ZoomOutListener |
appgvSIG |
Igual a ZoomOutListenerImpl actualizando los controles disponibles de
la interfaz gráfica de gvSIG para trabajar con la información actual en
el objeto MapControl asociado. |
Sí. |
PointEvent |
|
ZoomOutRightButtonListener |
libFMap |
Funciona igual que ZoomOutListenerImpl, pero sólo como respuesta
al pulsado del botón derecho del ratón. |
Sí. |
PointEvent |
|
ZoomPixelCursorListener |
appgvSIG |
Realiza una operación de zoom acercar calculando el nuevo extent del
ViewPort del objeto MapControl asociado centrado en su pixel más
al punto seleccionado. |
No. |
PointEvent |
|
Diagrama con herramientas que implementan PolylineListener
El diagrama 5 muestra los tool listener definidos en la librería libFMap asociados a una selección por polilínea.
Tipo |
Proyecto |
Descripción |
Cancelable |
Eventos a los que responde |
Icono |
AreaListenerImpl |
libFMap |
Calcula perímetro y área de la selección rectangular definida con dos vértices
de una de sus diagonales.
Si el objeto MapControl no está proyectado, toma las coordenadas como
geográficas (latitud - longitud), sino, las calcula teniendo en cuenta su
proyección y unidades de medida.
|
No. |
MeasureEvent |
|
AreaListener |
appgvSIG |
Igual que AreaListenerImpl actualizando la información de perímetro y área
en la barra de estado de la GUI de gvSIG. |
No. |
MeasureEvent |
|
MeasureListenerImpl |
libFMap |
Calcula y muestra por la salida estándar el valor de la longitud total y la del
último segmento de la polilínea definida por una serie de puntos. |
No. |
MeasureEvent |
|
MeasureListener |
appgvSIG |
Calcula y muestra por en el statusbar de gvSIG el valor de la longitud total
y la del último segmento de la polilínea definida por una serie de puntos. |
No. |
MeasureEvent |
|
PolygonSelectionListener |
libFMap |
Selecciona todas las features de las capas activas y vectoriales que
intersecten con el área poligonal definida sobre el objeto MapControl asociado.
La selección se producirá una vez se finalice la creacción de la polilínea.
|
No. |
MeasureEvent |
|
PolygonSelectListener |
appgvSIG |
Igual a PolygonSelectionListener actualizando los controles disponibles de
la interfaz gráfica de gvSIG para trabajar con la información actual en
el objeto MapControl asociado. |
No. |
MeasureEvent |
|
Diagrama con herramientas que implementan RectangleListener
El diagrama 6 muestra los tool listener definidos en la librería libFMap
asociados a una selección por área rectangular.
Tipo |
Proyecto |
Descripción |
Cancelable |
Eventos a los que responde |
Icono |
MapOverviewChangeZoomListener |
appgvSIG |
Permite realizar un cambio de extent según un área rectangular definida sobre
un objeto de tipo MapOverview.
Si la acción es un movimiento, y el objeto asociado es de tipo MapOverview
actualiza el extent manteniendo el zoom.
Si la acción es la selección de un área rectangular, y supera los 3x3 píxeles,
realiza una operación de zoom in adaptando el ViewPort de MapOverview
al extent equivalente en coordenadas del mundo al área seleccionada.
|
Sí. |
MoveEvent, RectangleEvent |
|
RectangleSelectionListener |
libFMap |
De las capas vectoriales que estén activas, selecciona todas las features que
intersecten con el área rectangular definida. |
No. |
RectangleEvent |
|
RectangleSelectListener |
appgvSIG |
Igual a RectangleSelectionListener actualizando los controles disponibles de
la interfaz gráfica de gvSIG para trabajar con la información actual en
el objeto MapControl asociado. |
No. |
RectangleEvent |
|
SaveRasterListenerImpl |
libFMap,
extRasterTools |
Almacena el área rectangular definida, en coordenadas del mapa y del mundo. |
Sí. |
RectangleEvent |
|
SaveRasterListener |
extRasterTools |
Permite guardar el área rectangular seleccionada, como un fichero raster, vía un
panel cone opciones de salvado. |
Sí. |
RectangleEvent |
|
ExportToGeoRasterToolListener |
extRasterTools |
El diálogo de recorte raster utiliza esta herramienta para permitir al usuario
seleccionar una nueva área y con ello actualizar las coordenadas de recorte. |
Sí. |
RectangleEvent |
|
ZoomInListenerImpl |
libFMap |
Realiza una operación de zoom acercar sobre el objeto MapControl
asociado. Para ello calcula el nuevo extent de su ViewPort según
las siguientes ecuaciones:
double factor = 1/MapContext.ZOOMINFACTOR;
Rectangle2D rect = event.getWorldCoordRect();
Rectangle2D.Double r = new Rectangle2D.Double();
ViewPort vp = mapCtrl.getMapContext().getViewPort()"
double nuevoX = rect.getMaxX() - ((vp.getExtent().getWidth() * factor) / 2.0);
double nuevoY = rect.getMaxY() - ((vp.getExtent().getHeight() * factor) / 2.0);
Rectangle2D.Double r;
r.x = nuevoX;
r.y = nuevoY;
r.width = vp.getExtent().getWidth() * factor;
r.height = vp.getExtent().getHeight() * factor;
vp.setExtent(r);
Hay que contar que el extent calculado no tendrá porqué coincidir con
el que en última instancia se visualize, dado que que ViewPort
se encargará de calcular el extent ajustado a partir de éste.
|
Sí. |
RectangleEvent |
|
ZoomInListener |
appgvSIG |
Igual a ZoomInListenerImpl actualizando los controles disponibles de
la interfaz gráfica de gvSIG para trabajar con la información actual en
el objeto MapControl asociado. |
Sí. |
RectangleEvent |
|
MapContext es una clase utilizada por MapControl para almacenar, gestionar y dibujar capas con información gráfica, así como los manejadores de eventos que se producen en ellas.
Contiene un ViewPort con la información necesaria para visualizar un área seleccionada de las capas en el área disponible para ello. Y contiene la conversión de las unidades definidas en el ViewPort, a metros o a centímetros. Véase el apartado Conversión de Unidades de Medida .
La librería libFMap permite trabajar con capas gráficas.
Estas capas gráficas se visualizarán de manera que un usuario pueda trabajar con ellas gracias a MapControl, que, siguiendo el patrón Modelo-Vista-Controlador, se desentiende de su almacenamiento (modelo), la gestión de eventos sobre ellas (parte del control), ni la transformación entre la parte que se quiere visualizar y la parte disponible para ello (otra parte del control, esta se encarga ViewPort), para encargarse solo de su visualización y la interacción con herramientas gráficas (vista, y parte del control).
Así pues, será MapContext quien se encargue de proporcionar a MapControl la lógica necesaria para almacenamiento de capas, la gestión de eventos en ellas y su dibujado, utilizando para ello un puerto de vista (ViewPort) y una proyección. Tal es así, que MapContext no puede existir fuera del contexto de MapControl .
MapContext soporta dibujado de las capas que almacena, estableciendo la calidad mediante antialiasing de texto e imágenes, y de renderizado, pero la lógica de dibujado está contenida en cada capa.
El diagrama 1 nos muestra una visión en conjunto de las principales clases e interfaces relacionadas con MapContext. Se puede así observar como MapContext, que implementa la funcionalidad definida en el interfaz Projected, es parte intrínseca de MapControl. También las relaciones con las capas que almacena (FLayers, y GraphicLayer), la información para dibujar el área seleccionada de las capas en el espacio disponible para ello (ViewPort), el buffer (EventBuffer) para tratar conjuntos de eventos recibidos de manera atómica, así como una clase interna para la manipulación genérica de los eventos en cualquier tipo de capa de éste (LayerEventListener).
Tenemos pues, una capa, GraphicLayer, propia de MapContext para los símbolos y geometrías editables, y un árbol con distinto tipo de capas gráficas. Ambas son opcionales, de manera que no es necesario que haya ambos tipos de capas a la vez.
Por último el diagrama se completa con los tipos de excepciones que puede lanzar trabajando con los eventos de las capas que contiene.
Se puede ver en el diagrama 2 como EventBuffer implementa interfaces para soportar eventos en una capa (LayerListener), en un conjunto de capas (LayerCollectionListener), del puerto de vista (ViewPortListener), de leyendas en capas (LegendListener), y producidos por la selección en una capa vectorial (SelectionListener).
Los eventos que recibe, los irá almacenando hasta que se le indique que los lance, lo cual aparentará externamente que se ha ejecutado un solo evento atómico.
EventBuffer lanzará un tipo de evento denominado AtomicEvent que contendrá una lista con todos los eventos almacenados entre las operaciones: beginAtomicEvent() y endAtomicEvent(). Además, se pueden registrar *listeners de tipo AtomicEventListener que permitirán realizar el tratamiento que se desee con este tipo de eventos. También, es posible cancelar en tiempo de ejecución el tratamiento de un evento de tipo AtomicEvent, en caso de producirse un error, se lanzaría una excepción de tipo CancelableException.
AtomicEvent
AtomicEvent es muy útil para invocar listeners una vez realizadas una serie de operaciones, evitando que se pudiesen invocar más veces, y que en el ejecución de alguna de estas, se llegase a algún estado inestable. Con ello evitamos también que puedan interferir o ralentizar el proceso de dibujado de capas, y a su vez, mejorar la interactividad.
Antes de empezar a recibir los eventos se debe activar el modo buffer en la instancia de EventBuffer invocando el método beginAtomicEvent(), y una vez se considere que ya no se van a recibir más eventos atómicos, se le debe de indicar que acabe el modo, invocando endAtomicEvent().
AtomicEvent es un tipo de evento propio de la librería libFMap, este tipo de eventos se define genéricamente en la clase FMapEvent.
Pulse aquí si desea ver el diagrama 2 ampliado.
MapContext, por otro lado, permite registrar listeners de eventos de recepción de cambios en las leyendas de las capas (LegendListener), notificación que se ha pintado o se va a pintar una capa (LayerDrawingListener), o de eventos de errores producidos en cualquier operación de los componentes de MapContext (ErrorListener).
- Nodo raíz opcional de tipo FLayers para el árbol con las capas.
- Capa (GraphicLayer) opcional para geometrías y símbolos editables.
- ViewPort con información para que pueda dibujar MapControl la información seleccionada de las capas.
- Lista de listeners de tipo LegendListener, que se utilizan con los cambios en las leyendas de las capas.
- Lista de listeners de tipo LayerDrawingListener, que se utilizan para notificar que se ha pintado o se va a pintar una capa.
- Un buffer (EventBuffer) para almacenar una serie de eventos, y luego lanzarlos todos de una vez. Estos eventos pueden ser:
- Producidos en una capa (FLayer): LayerEvent.
- Producidos en un colección de capas (FLayers): LayerCollectionEvent, LayerPositionEvent.
- Producidos en leyendas de una capa de tipo clasificable: LegendChangedEvent, LegendEvent.
- Producidos en el puerto de vista (ViewPort): ExtentEvent, ColorEvent, ProjectionEvent.
- Producidos por la selección en una capa alfanumérica: SelectionEvent.
- Eventos atómicos: AtomicEvent.
- Un listener de tipo LayerEventListener, para la notificación de eventos en cualquier tipo de capa de las que contiene MapContext. El tipo de eventos es:
- Se ha añadido, movido o eliminado una capa del árbol de capas.
- Una capa está a punto de añadirse, moverse o eliminarse del árbol de capas.
- Ha cambiado la visibilidad de una capa.
- Ha cambiado la selección de capas en el árbol.
- Una lista con los errores producidos en cualquier capa.
- Una lista con los errores producidos en el mapa.
MapContext define dos vectores públicos (uno para metros y otro para centímetros) con los valores con que habría de dividirse un valor dado en una de las medidas soportadas (véase ViewPort) para obtener el equivalente en metros, o en centímetros.
- Vector para conversión a metros: MapContext.CHANGEM .
- Vector para conversión a centímetros: MapContext.CHANGE .
Medidas soportadas
Entre corchetes, el número indica la posición en el vector:
- [0]: kilómetro
- [1]: metro
- [2]: centímetro
- [3]: milímetro
- [4]: milla internacional estatutaria
- [5]: yarda
- [6]: pie
- [7]: pulgada
- [8]: grado - Esta unidad es calculada así: sabiendo que el radio aproximado de la Tierra es de R=6.37846082678100774672e6, queremos medir la distancia en línea recta entre dos puntos en su superfície. Si partimos de tener 2 puntos en la superfície de la Tierra que están a un grado entre ellos, la unión entre estos tres puntos (radio y los 2 puntos en la superfície) nos da un triángulo isósceles, que si lo dividimos por la mitad, nos dará 2 triángulos rectángulos, donde el ángulo menor es de 0.5 grados, y un cateto es el radio terrestre, y el otro la mitad de la distancia (D) que queremos calcular. Aplicando trigonometría y despejando D, tenemos que: D = 2 * (sin (1)) * R. Luego si invertimos este valor, sabremos cuántos grados equivalen a un metro, y éste es el valor que está almacenado en el vector. Idem para la conversión centímetros. Posteriormente, cuando se utilice este valor, se debe tener en cuenta la proyección que se está usando en el mapa, para obtener los grados según dicha proyección.
Ejemplos de uso
- 1 milla estatutaria internacional / MapContext.CHANGEM[4] = M1 metros
- 1 kilómetro / MapContext.CHANGEM[0] = M2 metros
- 1 grado / MapContext.CHANGEM[8] = M3 metros
- 1 milla estatutaria internacional / MapContext.CHANGE[4] = C1 centímetros
- 1 kilómetro / MapContext.CHANGE[0] = C2 centímetros
- 1 grado / MapContext.CHANGE[8] = C3 centímetros
- Atomicidad: iniciar o terminar un evento atómico usando EventBuffer, y agregar o eliminar listeners de tipo AtomicEventListener para manejar este tipo de eventos.
- Clonación: soporta dos modos, una clonación total, o una copia parcial con lo necesario para el pintado.
- Dibujado de capas: en principio, el dibujado que soporta es solo de las capas que almacena MapContext, con la lógica de dibujado de cada una. Para acelerar el proceso de dibujado, solo se dibujan o redibujan aquellas capas "sucias", por eso, MapContext soporta tres tipos de dibujado:
- Solo la capa con símbolos y geometrías.
- Dibujado de todas las capas que lo requieran (estén sucias).
- Dibujado de todas las capas. En este caso y en el anterior puede establecer la calidad de dibujado mediante antialiasing de texto y símbolos, y mediante la calidad del renderizado.
- Escala de la vista: obtener o establecer la escala del puerto de vista teniendo en cuenta la resolución en puntos por pulgada en la pantalla.
- Gestión de errores: agregar, obtener o borrar los mensajes de errores producidos.
- Gestión de las capas:
- Obtener, establecer o dibujar la capa de tipo GraphicLayer.
- Obtener las capas, asociar listeners o el buffer de eventos, o dibujarlas.
- Listeners: permite trabajar con distintos tipos de listeners:
- ErrorListener: agregar este tipo de listener, o notificar a todos estos listeners registrados de un conjunto de eventos de error producidos durante una transacción atómica.
- LayerDrawingListener y LegendListener: agregar, eliminar o invocar un listener de alguno de estos tipos.
- Persistencia: crear una nueva instancia de MapContext a partir de una entidad XML, o devolver una entidad XML que represente el objeto MapContext actual (con la menor información necesaria), esta entidad tendrá:
- className: el nombre completo de la clase.
- 1 rama que será la entidad XML del ViewPort interno.
- 1 rama que será la entidad XML a partir del nodo raíz del FLayers interno.
- Comparar con otro objeto MapContext: considerará igual al objeto actual si:
- Ambos objetos son igual según el método equals de la clase Object.
- Si tienen las mismas capas.
- O, si tienen el mismo número de capas, y con el mismo nombre.
- Proyección: establecer u obtener la proyección que se utilizará con todas la capas que almacena MapContext.
- Puerto de vista: establecer u obtener el puerto de vista (ViewPort) actual de MapContext.
- Otra funcionalidad:
- Crear un objeto MapContext a partir de un puerto de vista, y el árbol de capas actual.
- Obtener el extent (dimensión y posición del área) unión del de todas las capas.
- Obtener el extent seleccionado.
- Registar el LayerEventListener del MapContext a un nodo de tipo FLayers .
- Redibujado de todas las capas.
- Define el factor de zoom por defecto para alejar o acercar el encuadre del área visible:
- Factor de Zoom In (acercar): 2
- Factor de Zoom Out (alejar): 0.5
LayerEventListener
Clase definida en MapContext para tratar eventos producidos en cualquier tipo de las capas de éste.
En concreto trata eventos de:
- Se ha añadido, movido o eliminado capa.
- Se está a punto de a añadir, mover o eliminar capa.
- Ha cambiado la visibilidad de alguna capa.
ViewPort es la clase utilizada por todas aquellas que deseen realizar una transformación afín 2D entre 2 áreas rectangulares, obteniendo coordenadas de píxel a partir de las de la capa visual (llamémosle mapa).
Así, pues, y aunque se utiliza en otras partes de gvSIG, nos centraremos en el uso que hace la clase MapContext, que se encarga del almacenar la información necesaria para visualizar capas en vistas de gvSIG.
Un objeto de tipo MapContext contendrá una instancia de ViewPort, que servirá para que se pueda visualizar el área rectangular de trabajo seleccionada sobre las capas gráficas, en el área disponible (llamémosle imageSize) que hay para visualizarla en el objeto MapControl que los contiene.
Así pues, tenemos 2 planos: el del mapa, y el de la pantalla; y cada uno puede estar en distinta unidad de medidad. MapContext permitirá realizar la conversión de medidas, entre ambos planos vía su objeto tipo ViewPort, y teniendo en cuenta su proyección. ViewPort define varias unidades de medida: metro, kilómetro, yarda, milla estatutaria internacional, etc.
En ViewPort, al área seleccionada para visualizar del mapa, la llamaremos extent, a la disponible para visualizarla, de la pantalla, dimensionSize, y a la seleccionada ajustada para que sea proporcional en ancho y alto a la disponible, adjustedExtent.
Al realizar el ajuste, el área "extra" se rellenará con el mapa, o, en última instancia, con el color de fondo del objeto ViewPort.
ViewPort almacena en un objeto de tipo ExtentHistory los últimos extents, dando la opción de recargar el previo cada vez.
Para acelerar el dibujado, se busca pintar la menor información necesaria, de manera que si al realizar la transformación mapa a ventana hay puntos muy cercanos que representan el mismo píxel o tres píxels juxtapuestos, se pintará uno. Por ello, cada vez que ViewPort recalcula la tranformación afín, además de recalcular adjustedExtent y la escala entre este e imageSize, se calcula la distancia en el mundo real a que equivalen 1 o 3 píxels juxtapuestos.
- Área seleccionada a visualizar, en coordendas del mapa: extent.
- Dimensiones en pixels del área disponible para visualizarla: imageSize.
- Área que se utilizará para visualizar. Esta área es la seleccionada, ajustada al aspecto de la disponible, de forma que ampliará la seleccionada, y en caso que no hubiese suficiente, con el color de fondo: adjustedExtent.
- Escala entre el área ajustada y la disponible. Esta escala es igual para ancho y alto.
- Color de fondo por defecto.
- Transformación afín entre el área a visualizar ajustada (en coordenadas de mapa), y la disponible para visualizarla (en coordenadas de píxel).
- Proyección que se utiliza para obtener el área a visualizar.
- Unidad de medida de distancias en el área visualizada: distanceUnits.
- Unidad de medida en el mapa original: mapUnits.
- Lista de las últimas áreas visualizadas: extents. Permite ir a vistas previas.
- Posición del área visible, de la esquina donde empieza a visualizar el mapa: offset.
- Distancia en coordenadas del mundo equivalente a 1 pixel en la vista con el área a visualizar actual: dist1pixel.
- Distancia en coordenadas del mundo equivalente a 3 pixels en la vista con el área a visualizar actual: dist3pixel.
- Lista de listeners de tipo ViewPortListener asociados a este puerto de vista.
Las unidades de medida soportadas, tanto para distancias como para del mapa original son:
- Kilómetro: unidad métrica o de longitud equivalente a 1000 metros.
- Metro: unidad de longitud del Sistema Internacional, que originalmente se estableció como la diezmillonésima parte del cuadrante del meridiano terrestre, y hoy, con más precisión, se define como la longitud del trayecto recorrido en el vacío por la luz durante un tiempo de 1/299 792 458 de segundo.
- Centímetro: centésima parte de un metro.
- Milímetro: milésima parte de un metro.
- Millas estatutarias internacionales: the international statute mile is by international agreement. It is defined to be precisely 1,760 international yards (by definition, 0.9144 m each) and is therefore exactly 1,609.344 metres (1.609344 km).
- Yarda: medida de longitud equivalente a 0,914 m.
- Pie: antigua unidad de medida que aún se utiliza en algunos países anglosajones, equivalente a 1/3 yardas y a 12 pulgadas.
- Pulgada: medida inglesa equivalente a 1/12 pies, y a 25,4 mm.
- Grados: grados según la proyección actual.
- Áreas:
- Área disponible: indicar y obtener las dimensiones (en pixels) del área disponible de visualización. Está en coordenadas de imagen.
- Área seleccionada: indicar y obtener sus dimensiones y posición. Está en coordenadas del mundo, con su sistema de medida.
- Área seleccionada ajustada a una escala de la disponible: permite obtener sus dimensiones y posición. Está en coordenadas del mundo, con su sistema de medida.
- Configuración:
- Ajuste de Extent: adaptar o no el área a visualizar a una escala de la disponible antes de calcular la transformación afín.
- Listeners: lógica a ejecutar cuando se producen ciertos eventos sobre la vista. Permite agregar o eliminar listeners a la capa.
- Clonación: obtener otra capa idéntica e independiente de la actual.
- Cálculo de la Transformación: se realiza automáticamente al refrescar el puerto de vista, cambiar de extent, o cambiar de área disponible. Este proceso además recalcula la nueva área ajustada, la escala entre área ajustada y visible, y las distancias equivalentes en coordenadas del mundo a 1 y 3 pixels.
- Conversiones: ViewPort permite realizar conversiones entre datos (puntos, distancias o Rectangle2D (dimensión + posición de un área rectangular)) en coordenadas del mapa, y coordenadas de la imagen, o viceversa.
- Offset: indicar y obtener la posición donde empieza a situar el área seleccionada transformada en el área disponible.
- Persistencia: crear una nueva instancia de ViewPort a partir de una entidad XML.
- Proyección: indicar y obtener la proyección que se ha usado para obtener el plano a partir de la información gráfica original, del que se visualiza un área.
- Unidades de medida: explicadas en el apartado Unidades de Medida.
- Otra funcionalidad:
- Color de fondo: se puede cambiar y obtener el color de fondo por defecto.
- Distancias equivalentes a 1 o 3 pixels: obtener la distancia equivalente en unidades de medida del mundo a 1 o 3 pixels en el área de visualización.
- Obtener los extents previos almacenados.
- Cambiar al extent previo, recalculando transformación afín, etc.
- Refrescar, recalculando transformación afín, ...
- Cálculo de la distancia (en coordenadas del mundo) entre dos puntos de las capas gráficas originales. Para ello se tiene en cuenta la proyección con que se ha obtenido el plano a partir de dichas capas.
ViewPortListener
Interfaz para la captura y tratamiento de los eventos asociados a un ViewPort, que son:
- ColorEvent: cambio de color de fondo.
- ExtentEvent: cambio de área seleccionada.
- ProjectionEvent: cambio de proyección.
ExtentHistory
Diseñada para tener un historial de Rectangle2D que representan áreas rectangulares seleccionadas de capa/s gráfica/s.
Se gestionan de manera que se puede agregar nuevas, pero sólo almacena una cantidad máxima (por defecto 10), elimando las más antiguas, y obteniendo siempre la última agregada.
gvSIG permite trabajar con múltiples y diferentes tipos de capas, de manera que cada una, según su naturaleza, tiene unas propiedades y comportamiento que no tienen porqué coincidir con las de otro tipo. En cambio, todas las capas, al menos teóricamente, pueden estar en un reducido número de estados simultáneos. A cada conjunto posible de estados simultáneos se les denomina estatus.
A nivel genérico, el estatus de una capa está definido en la clase FLayerStatus del proyecto libFMap, como se observa en el diagrama 1.
Así, se observa como la clase abstracta con la implementación común para todas las capas, FLyrDefault, contiene y utiliza una instancia de tipo FLayerStatus que representa el estatus actual en que puede estar cualquier tipo de capa.
En la práctica, una capa dada solo podrá estar en un subconjunto de los posibles estatus, teniendo en cuenta su naturaleza.
El estatus de una capa almacena la información de las excepciones que se puedan ir produciendo cuando se utiliza el driver asociado a ella, este tipo de excepción es DriverException.
Estados
- Activa: la capa está seleccionada en el TOC.
- Visible: la capa tiene seleccionado su checkbox asociado en el TOC. En caso afirmativo, implica que se intentará pintar la capa a no ser que no estén disponibles los datos necesarios, cosa que sucede a veces con capas de servicios remotos.
- Disponible: la fuente con los datos de la capa está on-line (el driver ha conectado con ella exitosamente y está lista para que sus datos se puedan acceder sin problemas).
- Sucia: la capa requiere actualizarse. En el momento que una capa pasa a edición, cambia la transparencia, cambia la visibilidad, se ordena refrescarla, o se le indica por parte de un tipo de capa determinado, cuando, por ejemplo, sus datos han cambiado, la capa pasará a estar sucia, lo que obligará a que se repinte, para luego pasar a "no sucia".
- En TOC: la capa está registrada en el árbol de capas del TOC.
- Editándose: el contenido de la capa puede modificarse en este momento.
- Puede escribirse: hay driver de escritura para esa capa, y tiene permisos para modificarla.
- Caché de capas dibujadas: almacena varias imágenes para acelerar el pintado.
En este apartado se han colocado unos ejemplos visuales con los que un usuario podría conocer el estado de la capa/s con la que está trabajando en gvSIG:
Toda capa utilizada en gvSIG hereda de la clase FlyrDefault, que implementa la declaración genérica y común de capa: el interfaz FLayer .
FlyrDefault, es una clase abstracta, con métodos genéricos para trabajar con capas definidos en la interfaz FLayer, métodos genéricos para trabajar con drivers (para utilizar la capa), definidos en la interfaz Driver, y otros métodos propios.
FLayer, incluye los métodos necesarios para trabajar con proyecciones, definidos en la interfaz Projected, con otros para trabajar con capas.
Por otro lado, es posible tener una colección de capas como nodo de un árbol de capas, y así poder trabajar con distintos niveles. Para ello, se definió la clase FLayers, que hereda de FlyrDefault, e implementa la interfaz LayerCollection con los métodos para trabajar con un conjunto de capas.
Dado que las capas de un mismo nodo pueden ser de distinto tipo, FLayers implementa, por compatibilidad, las interfaces InfoByPoint y VectorialData. Así, es posible que algunas capas trabajen con datos vectoriales, por ello se implementa la interfaz VectorialData; y/o a la vez es posible que soporten la operación de obtención de información a partir de un punto, por ello se implementa la interfaz InfoByPoint.
El siguiente diagrama muestra gráficamente lo explicado, sin entrar en detalles acerca de métodos ni atributos, pero sí listeners que se pueden asociar a una capa, con los correspondientes eventos que pueden recibir, y los tipos de excepciones que se pueden llegar a lanzar en caso de producirse algún error trabajando con una capa:
El interfaz Projected
Permite obtener la proyección actual de la capa, o reproyectar a partir de unas coordenadas de transformación.
El interfaz FLayer
Define la funcionalidad básica para trabajar con cualquier tipo de capa:
- Persistencia: obtener o agregar información completa o solo parcial de la capa.
- Estado: hay posibilidad de obtener o insertar el estado completo, o solo de algún atributo:
- Activa: si está o no seleccionada en el TOC.
- Visible: una capa es visible si está seleccionado su checkbox asociado en el TOC, lo que implica que se intentará pintar la capa a no ser que no estén disponibles los datos necesarios, cosa que sucede a veces con capas de servicios remotos.
- Disponible: si la fuente de datos está on-line.
- Sucia: si requiere o no actualizarse.
- En TOC: si está o no en el árbol de capas del TOC.
- Editándose: si la capa está siendo o no modificada en este momento.
- Puede escribirse: si hay o no driver de escritura para esa capa, y tiene permisos para modificarla.
- Caché de capas dibujadas: si usa o no una imagen con bandas.
- Identidad: nombre o descripción de la capa.
- Carga/Recarga: leer la información de la capa y agregarla o actualizarla.
- Dibujado: lógica de dibujado de la capa.
- Proyección:
- Si es o no reproyectable la capa.
- Uso de coordenadas de transformación, o información de proyección para proyectarla.
- Uso de la lógica de control para reproyectarla.
- Listeners: lógica a ejecutar cuando se producen ciertos eventos sobre la capa. Permite agregar, obtener, o eliminar listeners a la capa.
- Escalas visibles: rango de escalas de zoom en que se verá la capa.
- Gestión de errores: agregar, u obtener los errores producidos al cargar o trabajar con la capa.
- Nuevas propiedades: agregar u obtener nuevas propiedades asociadas a la capa.
- Clonación: obtener otra capa idéntica e independiente de la actual.
- Otra funcionalidad:
- Estado correcto.
- Obtener el modelo de la capa.
- Obtener posición y dimensiones de la capa.
- Obtener la imagen de estado en el TOC.
- Obtener una capa compuesta a partir de esta capa (este método está pensado para utilizarse solo en ciertas capas, de tipo FLayers).
- Establecer u obtener el nodo padre de esta capa.
- Establecer u obtener una imagen en el TOC que represente el estado actual de la capa.
El interfaz Driver
Ubicado en la biblioteca libDriverManager, define un método para que todos los drivers puedan devolver su nombre identificativo.
La clase abstracta FlyrDefault
Mientras que FLayer es una definición de funcionalidad genérica para cualquier tipo de capa, FlyrDefault representa la capa genérica de la que se particularizará para WMS, WCS, raster, vectorial, capa de texto, colección de capas, etc.
Posee la misma funcionalidad que FLayer, y además agrega:
- Estrategia: establecer u obtener la estrategia para recorrer la información de la capa.
- Transparencia: establecer u obtener el nivel de transparencia.
- Capas virtuales: gestionar capas virtuales asociadas a la actual.
- Capa de texto: gestionar una capa de texto asociada a la actual.
- Otra funcionalidad:
- Notificar a la capa que va a ser añadida.
- Establecer la imagen de estado de la capa en el TOC.
El interfaz LayerCollection
Define métodos para trabajar con una colección de capas:
- Agregar, eliminar, mover, u obtener capas.
- Agregar, o eliminar listeners de eventos producidos sobre la colección de capas.
- Cambiar la visibilidad de todas las capas de la colección.
- Activar / desativar todas las capas de la colección.
- Obtener las capas visibles.
El interfaz InfoByPoint
Define un método para obtener información de un área de tolerancia centrada en un punto de la capa o capas.
El interfaz VectorialData
Permite utilizar el patrón software Visitor con la lógica que indica cómo procesar datos vectoriales. Además permite procesar todas las geometrías, solo aquellas indicadas, o solo aquellas que corten el perímetro de un rectángulo determinado.
La clase FLayers
Representa un conjunto genérico de capas (FLayer). Posee el mismo comportamiento que FLyrDefault, pero adaptándolo para soportar un árbol de nodos que puede ser cada uno, una sola capa, u otro conjunto (otro nodo de tipo FLayers). Además, incluye la funcionalidad de LayerCollection, InfoByPoint, y VectorialData, y permite:
- Agregar, reemplazar, o eliminar una capa del conjunto.
- Agregar una capa a partir de una entidad XML.
- Dibujar un conjunto de capas, o una capa compuesta.
Excepciones
El diagrama anterior nos muestra los tipos de excepciones que puede lanzar por defecto cualquier tipo de capa, siempre que se haya producido algún tipo de error. Se indica aquí un breve resumen de cada uno de ellas:
- CancelationException: problema cuando el usuario cancela una tarea.
- DriverException: problema en el código encargado de trabajar con un tipo de fuente de datos determinada.
- DriverIOException: similar a DriverException, pero particularizado a cuando se realiza una operación de lectura o escritura sobre la fuente de datos.
- EditionException: problema cuando una capa o tabla está en estado editable.
- Exception: cualquier tipo de exceptión producida.
- VisitException: problema utilizando el patrón software Visitor accediendo a la información las capas.
- XMLException: problema realizando el proceso un marshall o unmarshall.
- En este enlace se muestran unas capturas de gvSIG con las que un usuario puede conocer el estatus de una capa.
- Este otro enlace es una recopilación de los tipos de capas que se utilizan en gvSIG, o en proyectos relacionados, con una explicación de cada una.
- Visitor: información de este tipo de patrón software.
gvSIG pinta usando el patrón "SingleWorketThread".
Las peticiones de dibujado se encolan y se envían a un único thread que lanza el dibujado con un objeto de tipo Cancellable para permitir interrumpir ese dibujado.
El thread de pintado va recibiendo comandos de pintado e inmediatamente se pone a pintar en segundo plano sobre una imagen en memoria. Frecuentemente, y para observar el progreso (porque puede llegar a tardar varios segundos en terminarlo) MapContext renderiza esa imagen y se muestra en pantalla. Esto es cada 400 milisegundos (y a partir de v1.2 será configurable de manera que se puede ver el progreso en tiempo real) y en el momento en que el thread ha terminado de pintar.
El dibujado de las capas lo hace MapContext. MapContext puede pintar sobre un Graphics2D que provenga de una imagen (caso de las peticiones del MapControl) o sobre un Graphics2D que venga de otro sitio (por ejemplo al imprimir). MapContext tiene una colección FLayers, y cada capa tiene un método draw. Para terminar, existe un "StrategyManager" que, en función del tipo de capa, escoge una estrategia de pintado u otra (patrón "Strategy"). Las estrategias de pintado están en el paquete "operations", dentro de libFMap, y las más interesantes son ShpStrategy y DBStrategy.
Por otra parte, si se observa el interfaz de las capas (FLayers) se verá que el método draw (que dibuja cada capa) tiene un argumento Cancellable que tiene un método isCanceled(). Cuando se ha cancelado el pintado (sea por un zoom, un pan o cualquier otro evento que comporte que lo que hay actualmente ya no es válido y debe de recalcularse) entonces isCanceled() devuelve true. En cualquier otro caso devuelve false.
La agilización de la cancelación se realiza dentro de cada una de las iteraciones que se hace en el método draw y posteriores llamadas internas, comprobando que isCanceled() es true, y si lo es, se aborta todo el proceso y entonces se sale del método del draw, si no lo es, se continua con el renderizando. Si se cancela el pintado de todas las capas de manera encadenada, la petición de draw del MapContext se termina, haciendo que se atienda a la siguiente (la que ha provocado que Mapcontext se invalide y que cancel.isCanceled() devuelva true).
Que el cancelado sea inmediato o no, depende del tipo de capas y de la implementación del método draw de ellas. La mayor parte de las capas tienen en cuenta el Cancelable, pero hay algunas que no lo pueden comprobar e inevitablemente ejecutan su método draw íntegro aunque el resultado no se llegue a mostrar en pantalla.
El método isWritable() de una capa devuelve true si el driver en el que se apoya la capa devuelve true:
public boolean isWritable() {
VectorialDriver drv = getSource().getDriver();
if (!drv.isWritable())
return false;
// VectorialEditableAdapter vea = (VectorialEditableAdapter) lyrVect
// .getSource();
if (drv instanceof IWriteable)
{
IWriter writer = ((IWriteable)drv).getWriter();
if (writer != null)
{
if (writer instanceof ISpatialWriter)
return true;
}
}
return false;
}
- Para el caso de los shape: devuelve true si el fichero tiene permiso de escritura. El resto (instanceof IWriteable) es por si tenemos solo capas con driver de lectura. Si el driver implementa la escritura, se podrá editar y guardar la capa en ese formato. Si no, la capa se puede poner en edición, pero no se puede salvar a ese formato, solo se puede guardar como .
- Strategy : explicación detallada en inglés acerca de este patrón sofware.
- WorkerThreadPattern : código Java con un ejemplo donde se implementa este patrón software.
- Painting in AWT and Swing : descripción detallada en inglés de los mecanismos y filosofía de pintado de la biblioteca Swing y el kit de herramientas AWT en Java.
- Pintando en AWT : descripción detallada en castellano de los mecanismos y filosofía de pintado en Java con la biblioteca AWT.
- Pintando en Swing : descripción detallada en castellano de los mecanismos y filosofía de pintado en Java con la biblioteca Swing.
Principales tipos de capas
En este apartado se describe brevemente los principales tipos de capas que, heredando de FLayer, FLyrDefault, o, FLayers, se implementan en la librería libFMap o en algún otro proyecto de gvSIG . Pulsa aquí para visualizar el diagrama 2 en grande.
Nombre |
Ubicación |
Descripción |
FFeatureLyrArcIMS |
extArcIMS |
Capa de tipo FLyrVect con soporte de cancelación de proceso de cargado, reimplementada para soportar las particularidades del servicio ArcIMS (Arc Internet Map Server): protocolo, estructura de datos, y su manipulación, teniendo una capa de tipo vectorial. |
FFeatureLyrArcIMSCollection |
extArcIMS |
Capa de tipo FLayers que la reimplementa para soportar un conjunto de capas de tipo FFeatureLyrArcIMS. |
FLayers |
libFMap |
Capa que representa una colección genérica de capas, e implementa el código necesario para trabajar con ellas. |
FLayerFileVectorial |
libFMap |
Capa de tipo FLyrVect utilizada para cargar ficheros con información vectorial. |
FLayerGenericVectorial |
libFMap |
Capa de tipo FLyrVect con un driver asociado. |
FLayerVectorialDB |
libFMap |
Capa que extiende de FLyrVect utilizada para capas que utilicen JDBC, como son PostGIS, MySQL, Oracle Spatial, etc ... |
FLyrAnnotation |
libFMap |
Capa de tipo FLyrVect utilizada para representar anotaciones textuales. Mantiene el tamaño de las geometrías dibujadas independientemente del zoom. |
FLyrGT2 |
libFMap |
Capa de tipo FlyrSecuential utilizada para pruebas. |
FLyrGT2_old |
libFMap |
Capa de tipo FLyrDefault utilizada para pruebas. |
FLyrRaster |
libFMap |
Antigua capa para trabajar con imágenes raster, que era ofrecida por la librería libFMap. De tipo FLyrDefault reimplementada para soportar operaciones propias de imágenes raster. Permite además:
- Indicarle u obtener su estatus.
- Agregar o eliminar ficheros a la capa raster.
- Indicar u conocer si será destruida la memoria del raster al eliminarlo del TOC.
- Conocer si la imagen está o no georreferenciada.
- Obtener el grid asociado.
- Crear un buffer para tener un driver raster de memoria.
- Indicar el extent (área seleccionada de trabajo de la capa).
|
FLyrRasterSE |
extRasterTools-SE |
Sustituye a la antigua FlyrRaster, y deja de estar en la librería libFMap. Aporta multitud de mejoras para dar soporte a la gran cantidad de formatos raster existentes. |
FLyrSecuential |
libFMap |
Definición abstracta de capa con soporte para datos vectoriales, y con una tabla de datos alfanuméricos asociada. |
FLyrText |
libFMap |
Capa de tipo FLyrDefault que acaba dibujando una serie de FShape's de formas geométricas creados a partir de la información de alto, ancho, posición, y rotación del contenido de una capa vectorial de tipo FLyrVect. |
FLyrVect |
libFMap |
Capa vectorial genérica: tiene soporte para edición, selección, reproyección, índices vectoriales, leyendas, datos alfanuméricos, acceso aleatorio a datos vectoriales, información por punto, e impresión. Además se le puede establecer la estrategia de recorrido, manipulación y pintado de sus features, y consultar por área rectangular, punto y shape. También realiza un ''marshall'' de su información, para poder ser salvarda en un fichero ".gvp" de gvSIG y así restaurarla posteriormente. |
FLyrWCS |
extWCS |
Capa de tipo FLyrDefault adaptada a las particularidades de WCS (Web Coverage Service), y comportándose como una capa de tipo raster. |
FLyrWFS |
extWFS2 |
Capa de tipo FLyrDefault adaptada a las particularidades de WFS (Web Feature Service), y comportándose como una capa de tipo vectorial. |
FLyrWMS |
extWMS |
Capa de tipo FLyrDefault adaptada a las particularidades de WMS (Web Map Service), y comportándose como una capa de tipo raster. |
FRasterLyrArcIMS |
extArcIMS |
Capa de tipo FLyrDefault con soporte de cancelación de proceso de cargado, reimplementada para soportar las particularidades del servicio ArcIMS (Arc Internet Map Server): protocolo, estructura de datos, y su manipulación, teniendo una capa de tipo raster. |
GraphicLayer |
libFMap |
Capa con ítems gráficos, que son geometrías con un símbolo y manejadores asociados.
Los ítems internos son independientes entre sí, y pueden ser seleccionados separadamente. Utiliza un bit set para definir los ítems que se seleccionan. Y aquellos que sean seleccionados, se dibujarán con manejadores, que según la implementación particular de cada uno, pueden mover el ítem, centrarlo, hacer pinzamientos sobre puntos, etc.
|
Resumen
A día de hoy, las Infraestructuras de Datos Espaciales (IDEs) [1] son consideradas el futuro para la búsqueda y el acceso a los recursos cartográficos, una de las pruebas es la directiva europea INSPIRE [2] por la que se establece un marco legal para la creación de una IDE europea. Esta extensión de publicación se trata del desarrollo hecho conjuntamente por la Consellería de Infraestructuras y Transportes de la Generalitat Valenciana y la empresa IVER T.I para la creación de una herramienta capaz de publicar datos cartográficos en una IDE utilizando gvSIG [3] como software base.
gvSIG es un GIS de escritorio, capaz de visualizar datos vectoriales en formatos estándar GIS y CAD, ráster y bases de datos con información espacial. Puede imprimir mapas, editar tanto ficheros de CAD, GIS así como bases de datos geoespaciales además de actuar como cliente avanzado de Infraestructuras de Datos Espaciales. El desarrollo software del que hablaremos en este artículo es un módulo que hace de gvSIG el centro de publicación de cartografía en internet mediante servicios estándar OGC[4] WMS (Web Mapping Service)[5], WFS (Web Feature Service)[6] y WCS (Web Coverage Services)[7], así como su notificación a servicios de catálogo accesibles mediante z39.50 o CSW (Catalogue Services Web)[8].
Este módulo (extensión de publicación de gvSIG) genera de manera transparente para el usuario los ficheros de configuración que utilizan Mapserver [9], Geoserver [10], Deegree[11] o Geonetwork[12], las
aplicaciones web más comunes para proporcionar los mencionados servicios. De esta forma, sin un conocimiento específico de estas aplicaciones, el usuario de gvSIG es capaz de publicar en internet, con extrema sencillez, la cartografía que genera.
Introducción
Las IDEs son un conjunto de tecnologías y prácticas institucionales que permiten a los usuarios buscar, obtener y compartir información geoespacial. Hasta ahora el método clásico de acceso a dicha información era a través de ?ficheros locales? que seguían cierto formato como pueden ser shapefiles, dgn, geotiff, etc. Con este nuevo paradigma de acceso a la información, el usuario ?se conecta? a los proveedores distribuidos de información que mantienen los datos, evitando la duplicidad y el acceso a datos obsoletos o incorrectos, entre otras ventajas. Aunque ya existían bases de datos y protocolos para el acceso a recursos a través de la red, la fuerza del paradigma IDE está en el hecho de definirse sobre estándares y mecanismos de interoperabilidad entre sistemas. Los estándares permiten crear sistemas interoperables, es decir, capaces de comunicarse entre sí ya que ?hablan el mismo idioma? y siguen las mismas reglas, por tanto podremos crear nodos de información geográfica que pueden comunicarse con otros nodos formando una red de sistemas proveedores de información. Esto permitirá la creación de IDEs en distintos ámbitos, por ejemplo a nivel institucional, local, regional, nacional y global, que al estar interconectadas entre sí, facilitará a los usuarios el acceso a cualquier recurso cartográfico publicado. Se consigue de esta manera un sistema escalable de acceso estándar a gran cantidad de información.
Algunos estándares OGC:
Especificación |
Versión actual (Mayo 2007) |
Descripción |
WMS |
1.3.0 |
Acceso a mapas, fotografías de la información cartográfica |
WFS |
1.1.0 |
Acceso riguroso a información vectorial |
WFS-G |
0.9.3 |
Acceso a recursos a partir de un topónimo |
WCS |
1.0.0 |
Acceso riguroso a información ráster |
CAT |
2.0.1 |
Acceso a metadatos, búsquedas en un catálogo de recursos |
Estos estándares, definidos es su mayoría por el Open Geospatial Consortium (OGC) (ver tabla anterior), son implementados en su parte servidor por productos como Mapserver, Geoserver, Deegree y Geonetwork, entre otros. Estas implementaciones forman parte fundamental (junto a las aplicaciones cliente) de la tecnología que forma una IDE. El modulo de gvSIG que aquí se presenta trata de configurar o poner en funcionamiento estos servidores de una forma sencilla para el usuario. Una de las consecuencias de las especificaciones estándar es la
posibilidad de desarrollar la tecnología subyacente a las IDEs por parte de varios proveedores, es decir, tanto Mapserver como Geoserver pueden implementar la especificación del estándar WMS, lo que permite a los usuarios disponer de varias alternativas. Si además estos productos son de software libre el avance tecnológico no está limitado por las empresas sino que lo está por las demandas de los usuarios y la evolución de los estándares.
Objetivos
El principal objetivo de esta extensión es la de poder publicar en un servicio OGC estándar la información cartográfica que un usuario está visualizando en una vista de gvSIG. Para ello, esta extensión tendrá que generar los ficheros de configuración necesarios correspondientes al servidor y servicio seleccionado.
Algunas consecuencias
- Promover el desarrollo de las IDE.
El hecho de que existan varias alternativas a la hora de elegir el software que implementan las especificaciones, implica el conocimiento de la tecnología de cada producto, por ejemplo, si decido utilizar Mapserver como servidor WMS, Geoserver como WFS, Deegree como WFS-G y Geonetwork como CSW2, tendré que emplear distintas herramientas para la configuración y puesta en funcionamiento de cada uno de los servicios basándose en un conocimiento técnico de cada uno ellos. Este escenario hace difícil, incluso para un usuario con conocimientos de GIS, poder publicar su cartografía. Uno de los objetivos de la extensión de publicación es la de fomentar la implantación de IDEs poniendo a disposición de la comunidad de usuarios un herramienta integral para la publicación de sus mapas, metadatos, coberturas y todo tipo de información susceptible de ser accedida mediante un servicio estándar. Si un usuario GIS está acostumbrado al uso de gvSIG, el publicar sus datos será muy sencillo. Por otra parte la facilidad de uso del programa hace que, sin grandes conocimientos de GIS, sea factible disponer de un servicio de mapas, de catálogo, etc.
- Uso eficiente de los recursos.
Con una herramienta integral para la publicación de información en los servidores de una IDE, el recurso humano con perfil de informático es, entre otras muchas cosas, el responsable de instalar y mantener el software de los servidores, bases de datos , etc, mientras que el recurso con perfil GIS es el encargado de alimentar de datos esa infraestructura. En esta situación el usuario GIS delega la responsabilidad de conocer tecnología específica de sistemas de información y se centra en la componente geográfica. Por otra parte estamos ahorramos tiempo ya que la generación de la configuración de los servidores es casi automática.
Referencias
- Recurso cartográfico: información sobre una entidad georeferrenciada . Por ejemplo un mapa, una feature, una cobertura, un metadato,etc.
- Service: protocolo de comunicación en la arquitectura cliente-servidor.
- Server: producto software que implementa la especificación de un servicio.
- Publicación: generación de los recursos necesarios (ficheros de configuración) para que funcione correctamente un servidor.
- Feature: Entidad cartográfica vectorial como un punto, línea o polígono.
- Topónimo: nombre que identifica una feature.
- Metadato: Información asociada a un recurso cartográfico.
- WMS: Servicio de mapas. El cliente se comunica con el servidor para obtener "fotografías" de la cartografía.
- WFS: Servicio de features. El cliente se comunica con el servidor para obtener cartografía vectorial.
- WCS: Servicio de cobeberturas. El cliente se comunica con el servidor para obtener cartografía en forma de coberturas ráster.
- WFS-G: Servicio de nomenclátor. Perfil (caso de uso específico) del protocolo WFS en el que el cliente obtiene un feature a partir de un topónimo.
- CS-W: Servicio de catálogo. El cliente puede realizar búsquedas sobre infromación cartográfica en un catálogo de metadatos.
- SLD: Descriptor de estilos de capas. Especificación de cómo debe definirse la leyenda y simbología de una capa WMS.
- Mapfile: Fichero de configuración de Mapserver.
- SRS: Sistema de referencia espacial. Sistema de coordenadas de cualquier georreferenciación.
- Leyenda
- Símbolo
Convenciones UML
- <<analysis subsystem>>
Esteroripo utilizado para especificar subsistemas de análisis. Estos subsistemas representan la arquitectura del sistema a alto nivel. Dicho subsistema agrupa clases de análisis.
IMPORTANTE: Un subsistema de análisis NO ES UN PAQUETE JAVA.
- <<boundary>>
Clases del análisis que representan la frontera de un subsistema. Cuando hablemos de subsistemas de interfaz de usuario representan las ventanas y controles con los que trabaja el usuario. En el caso de ser un otro tipo de subsistema distinto, se trata de los interfaces públicos.
IMPORTANTE: Una clase del análisis NO ES UNA CLASE JAVA.
- <<control>>
Clases del análisis que encapsulan la funcionalidad del sistema. En subsistemas de interfaz de usuario recogen e interpretan los eventos del usuario. En otro tipo de subsistemas se encargan de realizar cálculos y colaboraciones entre objetos del modelo.
IMPORTANTE: Una clase del análisis NO ES UNA CLASE JAVA.
- <<entity>>
Clases del análisis que representan el modelo de la aplicación.
IMPORTANTE: Una clase del análisis NO ES UNA CLASE JAVA.
#2 |
Soporte para múltiples servidores |
La extensión tiene que ser capaz de generar configuraciones en distintos tipos de servidores. En principio, la arquitectura debería estar diseñada para realizar las configuraciones de Mapserver, Geoserver, Deegree y Geonetwork.
|
#3 |
Soporte para múltiples servicios |
La extensión generará las configuraciones de varios servidores para que den servicios como WMS, WFS, WCS, CS-W y WFS-G. La extensión debe estar preparada para añadir nuevos servicios.
|
#4 |
Publicación de la vista 2D de gvSIG. |
La extensión generará la publicación a partir de la vista 2D activa de gvSIG. Se pretende que se reproduzcan las capas de la vista en recursos remotos a traves de servicios como WMS, WFS o WCS.
|
#5 |
Leyendas. Soporte para múltiples leyendas |
La leyenda que permite gvSIG se deberá reproducir en las configuraciones de los servidores. Existirán limitaciones, por ejemplo, la norma SLD no contempla los tamaños de símbolos en unidades distintas a píxeles. Las leyendas a configurar serán:
- Leyenda simple
- Leyenda de valores únicos
- Leyenda por intervalos
Puesto que gvSIG aceptará más tipos de leyendas, el sistema debe admitir esta escalabilidad.
|
#6 |
Simbología. Soporte para múltiples símbolos. |
Actualemente gvSIG soporta la siguiente simbología:
Los puntos podrán tener color RGB, tamaño tanto en píxeles como en metros y un símbolo bien conocido (cuadrado, círculo, triángulo, cruz o imagen). Las líneas podrán ser definidas mediante un color, una transparencia, un grosor en píxeles y un tipo bien conocido (ver posibilidades de gvSIG). Finalmente, los polígonos se pueden visualizar mediante una línea exterior con los parámetros mencionados anteriormente y unos parámetros propios como el patrón de relleno (ver tipos disponibles en gvSIG), una transparencia y un color de relleno.
Habrá que tener en cuenta que en el futuro la simbología se ampliará.
|
#7 |
GUI para seleccionar el servidor. |
La extensión tendrá un interfaz de usuario que permita seleccionar el servidor en el que se quiere generar la configuración. En el siguiente paso el usuario ya selecciona los servicio a configurar asñi como los parámetros específicos de dicho servidor.
El usuario seleccionará la URL del servidor, si ese servidor ya fue configurado debería recordar sus parámetros específicos. Si además esas misma capas fueron configuradas previamente, el sistema debería recordar sus parámetros.
|
#8 |
GUI básico de configuración de Mapserver |
Este interfaz de usuario debe permitir al usuario suministrar la siguiente información:
- Directorio donde se generan los ficheros de configuración.
- Directorio temporal.
- Servicios que desea configurar.
|
#9 |
Etiquetado. |
Se podrá realizar un etiquetado de geometrías vectoriales en el que se podrá especificar el campo a etiquetar, el campo que indica la rotación , el tamaño en pixeles o metros, el campo de la altura del texto, el color del texto y la fuente.
|
#10 |
GUI de navegación por las capas a publicar |
Debe de crearse un interfaz de usuario que permita navegar por las capas que se van a publicar, es decir, los recursos remotos. Mediante esta navegación el usuario podrá definir los parámetros de ese elemento. Los principales elementos son:
* Pa?ametros del servicio
* Parámetros de los elementos que contiene dicho servicio
|
#11 |
Iguales configuraciones en distintos servidores. |
El sistema debe permitir generar una configuración y guardar el estado de ésta para poder realizar la misma configuración en otro servidor. Esto significa que el sistema debe poder almacenar toda la información que es posible compartir entre varias configuraciones, por ejemplo el titulo y el abstract de las capas WMS.
|
#12 |
Fuentes de datos vectoriales básicas |
La publicación se podrá realizar con las fuentes de datos que tanto gvSIG como el servidor sea capaz de leer. En algunos casos habrán fuentes de datos que sólo una de las partes podrá leer, en este caso se deberá notificar al usuario. Existirán limitaciones porque, a priori, no existe una forma de conocer los formatos soportados en el lado servidor.
Las fuentes vectoriales básicas a soportar son las siguientes:
* Shapefiles
* Base de datos PostGIS
|
#13 |
Fuentes de datos ráster básicas |
La publicación de un servicio se podrá realizar con las fuentes de datos que tanto gvSIG como el servidor sea capaz de leer. En algunos casos habrán fuentes de datos que sólo una de las partes podrá leer, en este caso se deberá notificar al usuario. Existirán limitaciones porque, a priori, no existe una forma de conocer los formatos soportados en el lado servidor.
Las fuentes de datos ráster básicas a soportar son las siguientes:
* ECW
* Geotiff
|
#14 |
GUI Mapserver WMS |
Se creará un interfaz de usuario que permita al usuario ver los parámetros del servicio WMS a configurar.
- Nombre
- Título *
- Resumen *
- Palabras clave *
También podrá especificar los parámetros específicos de cada una de las capas WMS:
- Nombre
- Título *
- Resumen *
- Palabras clave *
- ¿puedes consultar información? (isQueryable)
- ¿es opaca ?
Mapserver tiene parámetros específicos que no contempla la norma WMS y que deberían poderse configurar también:
Se debe poder navegar a través de las capas con un árbol de recursos remotos.
NOTA: Los señalados con un asterisco se podrán modifcar.
|
#16 |
GUI Mapserver WCS |
TODO
|
#37 |
Configuración outputformats en Mapserver |
El GUI de Mapserver debe permitir definir los outputformats necesarios para tener un servicio WCS con los formatos de salida más comunes, al menos, Geotiff (int16) y Geotiff (float32).
|
La funcionalidad de esta extensión la podemos dividir en unos caso de uso de alto nivel y unos casos de uso específicos asociados a cada tipo de servidor concreto.
Casos de uso de alto nivel
La funcionalidad a más alto nivel que espera el usuario de esta extensión es la de publicar la vista activa de gvSIG en un servidor para poder acceder a los recursos cartográficos de forma remota. En cuanto a publicar una vista nos referimos a generar los ficheros de configuración necesarios para que un servidor pueda suministrar la misma información (imágenes, coberturas, features, ...)que el usuario está visualizando en la vista de gvSIG. La vista se podrá publicar en distintos servidores como Mapserver o Geoserver.
En términos generales, publicar una vista consistirá en primer lugar en especificar el servidor que queremos configurar. Esto básicamente es indicar la URL del servidor y marca , por ejemplo, Geoserver en
http://localhost/geoserver.
Tras ello, deberemos especificar los parámetros específicos del servidor seleccionado, por ejemplo, aquí tendremos que especificar los servicios que queremos configurar ya que cada tipo de servidor implementa unos servicios específicos. En esta actividad también se tendrá que indicar los parámetros para cada servidor en concreto, por ejemplo, en Mapserver tendremos que notificar al sistema que el directorio temporal que debe emplear. La siguiente actividad será especificar los parámetros específicos de cada uno de los servicios y de los recursos remotos que queremos generar. Por ejemplo, si queremos configurar un servicio WMS, tendremos que establecer el título, resumen, palabras clave, etc, del servicio y de cada una de las capas. Finalmente se publicará la vista, es decir, se generará los ficheros de configuración del servidor seleccionado.
Por otra parte, la extensión debe ser extensible en cuanto a funcionalidad. Por ejemplo, inicialmente publicaremos servicios WMS y WCS en Mapserver pero puede que en un futuro lo haga también en WFS, por ello cada servidor tiene unos puntos de extensión asociados como se muestra a continuación.
Casos de uso asociados a Mapserver
Casos de uso asociados a Geoserver
La arquitectura del sistema se puede dividir en cuatro bloques de alto nivel. El interfaz de usuario, la librería servidores, un bloque horizontal que proporciona información sobre los recursos que se quieren publicar y finalmente los plugins, que en esta primera versión será el de Mapserver y Geoserver.
gui
Este bloque representa el interfaz de usuario de la aplicación. Tiene la responsabilidad de obtener la información que aporta el usuario y que es necesaria para realizar una publicación.
modelServers
Este subsistema representa el modelo de servidores. Tiene la responsabilidad de generar los ficheros de configuración necesarios para el funcionamiento del servidor correspondiente.
infoProject
Este último bloque será empleado por las librerías de los servidores para obtener información sobre los recursos que se desean publicar, en concreto, sobre el proyecto que se quiere publicar, es decir, capas, leyenda, simbología, etc. Esta información provendrá del proyecto y vista activa de gvSIG.
plugins
Este susbsistema representa la parte del sistema que extenderá el framework compuesto por los tres subsistemas anteriores para poder hacer la publicación en un servidor concreto. En esta primera versión se realizará un plugin para Mapserver y otro para Geoserver.
Las pruebas que se describen a continuación hacen referencia a los test automáticos que intentan validar la correcta implementación de la funcionalidad de la extensión, es decir, la correcta generación de los ficheros de configuración. Por tanto, habrá un caso de prueba por cada uno de los casos de uso de alto nivel que describíamos anteriormente.
Con estas pruebas conseguiremos validar la correcta integración de los distintos subsistemas, exceptuando el gui que llevará un tratamiento especial en cuanto a pruebas automáticas.
Cuando entremos en detalle en cada subsistema o bloque, especificaremos con más detalle las pruebas asociadas a cada bloque en particular. En estos test de más bajo nivel se intentará validar la funcionalidad específica de cada subsistema.
En esta primera versión de la extensión de publicación, se va a desarrollar el plugin para Mapserver (WMS y WCS) y el plugin para Geoserver (WFS). Por tanto, y viendo los casos de uso de alto nivel, los casos de prueba son los siguientes:
Caso de prueba ?publish Mapserver WMS? (test_publishMapserverWMS)
En este conjunto de pruebas se deberá considerar los siguientes aspectos relacionados con los requisitos:
- Publicación de distintas fuentes de datos tales como shapefiles, postGIS y ráster ECW y Geotiff.
- Publicación de la leyenda, valores únicos e intervalos.
- Publicación de la simbología para puntos, líneas y polígonos
- Publicación de capas anidadas.
Por tanto se espera que tras la ejecución de este test se ha creado un fichero mapfile y otro sysbolsfile con la configuración de un servicio WMS de Mapserver y que cumple con los puntos mencionados anteriormente.
Caso de prueba ?publish Mapserver WCS? (test_publishMapserverWCS)
Aquí nos centraremos en comprobar la correcta configuración del mapfile para que puedan obtenerse coberturas en formato Geotiff.
Caso de prueba "publish all services in Mapserver" (test_publishMapserver)
Puesto que Mapserver puede definir en el mismo fichero Mapfile varios servicios, en este test se espera obtener un fichero mapfile" que contenga la configuración básica de un servicio WMS y otro WCS. Para ello se puede publicar una vista con un shapefile con leyenda simple y un raster.
Caso de prueba ?publish Geoserver WFS? (test_publishGeoserverWFS)
En este caso nos centraremos en la correcta generación de los ficheros de configuración de Geoserver, considerando todas las fuentes de datos que se definen en los requisitos, shapefiles y postGIS. Con este test se pretende obtener un fichero services.xml, catalog.xml y un fichero info.xml por cada capa wfs.
TODO: Clases de apoyo para los test
Contexto
El modelo de servidores es el núcleo principal de la extensión ya que es responsabilidad suya el generar los ficheros de configuración. Para generar estos ficheros, empleará la información proveniente del subsistema infoProject más la que le proporciona el usuario a través del subsistema gui.
Las entidades más importantes son el servidor (Mapserver, Geoserver, ...) que puede ofrecer varios servicios (WMS, WFS, WCS, ...). Además, un servicio está compuesto por una serie de recursos remotos a los cuales accederá el cliente , estos recursos remotos son, por ejemplo, una capa WMS, una cobertura WCS, una feature WFS, un metadato CS-W, ...
Un recurso remoto puede a su vez contener más recursos remotos. Un ejemplo son los recursos remotos que ofrece un servicio WMS en los que una capa WMS puede ser el conjunto de varias capas.
Por otra parte, tenemos entidades OGC que tienen título, nombre y descripción. La intención es poder reutilizar esta información para realizar publicaciones en distintos servidores.
Funcionalidad
La funcionalidad que se espera de este subsistema es la de generar la publicación sin necesidad de un gui.
Se puede decir que el caso de uso de alto nivel publicar vista incluye los casos de uso que describimos a continuación.
El siguiente diagrama de casos de uso especifica la funcionalidad que debe cumplir cada una de las librerías de servidor:
El cliente de la librería, ya sea un gui u otro programa, en primer lugar especificará los parámetros del servidor incluyendo los servicios que quiere configurar. En segundo lugar tendrá que configurar cada uno de los servicios y los recursos asociados a él. Una vez el sistema tiene toda la información necesaria para la publicación ya se puede invocar la generación de la configuración.
Pruebas
Para cualquier librería que extienda el framework, se deberán de crear los siguientes test automáticos:
Caso de prueba "set server parameters" (test_set<server>Param)
Caso de prueba "set service parameters" (test_set<service>Param)
Caso de prueba "set resources parameters" (test_set<resource>Param)
Caso de prueba "make configuration" (test_make<server>Conf)
TODO: descripción de cada una de las pruebas
Diseño
Publication
Esta clase representa la publicación que se puede realizar en un servidor.
- Operaciones
setServer(s:Server) Inicializamos la publicación con un servidor
getServer():Server Obtenemos el servidor de la publicación
Excepciones
TODO
Server
Representa un servidor abstracto. Un servidor puede ofrecer varios servicios.
- Operaciones
Server() Constructor por defecto, inicializa el servidor con todos los servicios que se han registrado en él.
Server(service:String) Constructor en el que sólo se inicializa con el servicio especificado
getType():String Devuelve una cadena que identifica el tipo de servidor. Por ejemplo, "mapserver".
toString():String Sobreescribimos el método "toString" para obtener el nombre del servidor.
initialize(view:IViewInfo) Inicializamos el servidor con la información de una vista. Para cada servicio invocamos su metodo initialize()
postInitialize() Una vez inicializado el servidor este método comprueba que cada recurso remoto de sus servicios es compatible con el servidor. Para cada servicio invoca su metodo postInitialize().
addService(service:String) Añadimos el servicio a partir del nombre del servicio.
getService(service:String):Service Obtenemos un servicio del servidor a partir del nombre del servicio.
getService(position:int):Service Obtenemos un servicio del servidor a partir de un número que indica el orden en que se introdujo.
getServicesCount():int Obtenemos el número de servicios que tiene actualmente el servidor.
publish() Método abstracto para generar la publicación, es decir, los ficheros de configuración. Llama a los métodos "publish()" de todos sus servicios. Cualquier servidor heredado deberá sobrescribir este método para realizar las operaciones pertinentes, finalmente llamará al método del padre super.publish() para que se realice la publiación de los servicios.
Excepciones
TODO
Service
Clase que representa un servicio abstracto. Una servicio ofrece varios recursos remotos accesibles por un cliente de dicho servicio.
- Operaciones
getType():String Devuelve una cadena que identifica el tipo de servicio. Por ejemplo, "mapserver_wms".
toString():String Sobreescribimos el método "toString" para obtener el nombre del servicio.
initialize(view:IViewInfo) Inicializamos el servicio con la información de una vista. Para cada capa de la vista creamos un recurso remoto.
postInitialize() Una vez inicializado el servicio este método comprueba que cada uno de sus servicios remotos es compatible con el servidor y el servicio.
getRemoteResource(position:int):RemoteResource Obtenemos un recurso remoto del servicio a partir de un número que indica el orden en que se introdujo.
getRemoteResourcesCount():int Obtenemos el número de recursos remotos que tiene actualmente el servicio.
publish() Método abstracto para generar la publicación, es decir, los ficheros de configuración. Llama a los métodos "publish()" de todos sus recursos remotos.
Excepciones
TODO
RemoteResource
- Operaciones
getType():String Devuelve una cadena que identifica el tipo de recurso remoto. Por ejemplo, "mapserver_wms_layer".
toString():String Sobreescribimos el método "toString" para obtener el nombre del recurso remoto.
initialize(view:ILayerInfo) Inicializamos el recurso con la información de una capa. En el caso de capas anidadas creamos una recursos remoto por cada capa hija.
postInitialize() Comprobamos que todos sus recursos remotos hijos son compatibles con el servidor y el servicio.
getRemoteResource(position:int):RemoteResource Obtenemos un recurso remoto hijo a partir de un número que indica el orden en que se introdujo.
getRemoteResourcesCount():int Obtenemos el número de recursos remotos hijos.
publish() Método abstracto para generar la publicación, es decir, los ficheros de configuración. Llama a los métodos "publish()" de todos sus recursos remotos hijos.
Excepciones
TODO
ServerRegister
Clase encargada de registrar los servidores disponibles para la extensión. Cada plugin de servidor deberá registrarse en este registro.
- Operations
addServer(key:String, clase:Class) Registramos un nuevo servidor en la extensión a partir de una cadena que lo identifica (key).
getServerNames():Set Devuelve los nombres (keys) de todos los servidores registrados.
getInstance(key:String) Crea una instancia del servidor especificado por "key".
Excepciones
TODO
ServiceRegister
Clase encargada de registrar los servicios disponibles para la extensión. Cada plugin de servidor deberá registrar sus servicios en este registro.
- Operations
addService(key_server:String, key_service:String, clase:Class) Registramos un nuevo servicio correspondiente a un servidor a partir de una cadena que identifica el servidor (key_server) y al servicio (key_service).
getServiceNames(key_server:String):Set Devuelve los nombres (keys) de todos los servicios registrados para un servidor.
getInstance(key_server:String, key_service:String) Crea una instancia del servicio especificado por "key_service" correspondiente al servidor "key_server".
Excepciones
TODO
RemoteResourceRegister
Clase encargada de registrar los recursos remotos correspondientes a un servicio concreto. Cada plugin de servidor deberá registrar sus recursos remotos en este registro.
- Operations
addRemoteResource(key_service:String, key_remoteResource:String, clase:Class) Registramos un nuevo recurso remoto asociado a un servicio.
getRemoteResourcesNames(key_service:String):Set Devuelve los nombres (keys) de todos los recursos remotos asociados a un servicio.
getInstance(key_service:String, key_remoteResource) Crea una instancia del recurso remoto especificado por "key_remoteResource" asociado al servicio identificado por la clave "key_service".
Excepciones
TODO
Contexto
El principal objetivo de este subsistema es el de proporcionar un API para obtener información asociada a un proyecto, una vista, capas, fuentes de datos, leyendas, simbología o etiquetado. En la arquitectura de la extensión, se situaría como un servicio horizontal que proporciona de forma automática toda la información relevante para la publicación. Por ejemplo, si para generar la configuración de un servicio WMS necesitamos la ruta absoluta de un shapefile, habrá que consultar la parte correspondiente a fuentes de datos de este subsistema.
Como podemos ver en la siguiente figura, tenemos información sobre un proyecto y una vista. A su vez, una vista tiene información sobre un conjunto de capas que tienen información sobre su leyenda, la fuente de datos de la que proviene y sobre el etiquetado. Además una leyenda tiene la información referente a la simbología.
Funcionalidad
La funcionalidad que se espera de este subsistema es la de consultar información sobre los siguientes elementos:
- Proyecto: nombre y propietario.
- Vista: nombre, fecha de creación, propietario, sistema de coordenadas, caja de delimitación y capas.
- Capa: nombre, título, mínima y máxima escala, transparencia, caja de delimitación, fuente de datos, leyenda, etiquetado.
- Leyenda: En función del tipo de leyenda tendremos una información relevante.
- Leyenda simple: símbolo
- Leyenda de valores únicos: los símbolos de cada de uno de los valores únicos
- Leyenda de intervalos: los símbolos de cada intervalo.
- Simbología: Estos son los tipos de símbolos de los que podemos extraer información.
- Símbolo para líneas: color, grosor, unidades del grosor, patrón de discontinuidad, transparencia.
- Símbolo para polígonos: color, patrón de relleno, símbolo de la línea exterior.
- Símbolo para puntos: color, tamaño, unidades del tamaño, gráfico externo o figura conocida.
- Fuentes de datos: Se disponen de las siguientes fuentes de datos:
- Vectoriales: ruta al fichero, tipo de geometrías, descripción de la tabla de atributos, sistema de coordenadas, caja de delimitación. En el caso de ser un fichero tendremos la ruta a él y en el caso de ser una tabla de base de datos tendremos los parámetros de conexión o claúsula where.
- Raster: ruta al fichero, número de bandas, descripción de las bandas, resolución, caja de delimitación, sistema de coordenadas.
- Etiquetado. De momento sólo dispondremos del tipo de fuente, el color, el campo del etiquetado, el campo de la rotación, el campo del tamaño o un tamaño fijo en metros o píxeles.
Esta consulta de información se puede dividir en los siguientes casos de uso:
Diseño
Pruebas
De la misma forma que otros subsistemas, las pruebas automáticas que deberán hacerse en este subsistema vendrán definidas por los casos de uso o funcionalidad esperada de él. De esta forma, tendremos los siguientes casos de prueba:
Caso de prueba "query project" (test_queryProject)
Caso de prueba "query view" (test_queryView)
Caso de prueba "query layer" (test_queryLayer)
Caso de prueba "query data source" (test_queryDataSource)
Caso de prueba "query legend" (test_queryLegend)
Caso de prueba "query symbol" (test_querySymbol)
Caso de prueba "query labeling" (test_queryLabeling)
TODO: Detalles de cada prueba
Descripción
El interfaz gráfico de la extensión de publicación está compuesto por una ventana principal (MainWindow) y un controlador principal (MainController) encargado de dirigir la publicación.
Esta ventana principal contendrá una vista de un elemento de la publicación (PublishView), esto es, un servidor, una publicación entera con sus servidores, etc. Esta vista de publicación será propia de cada uno de los servidores ya que cada servidor puede tener distintos parámetros y acciones sobre sus vistas.
Para obtener estas vistas, cada servidor concreto tendrá que registrar sus controlador (PublishController) que a parte de obtener dichas vistas específicas, se encargará de recoger los eventos que ocurran en ellas. Nótese cómo este controlador será el que más relación tiene con el modelo (los servidores, servicios y remote resources). La idea es, por tanto, que el controlador principal obtenga el controlador específico de un servidor que le aportará los paneles o vistas concretas para esa implementación.
Por otra parte, y como puede verse en diagrama, tendremos una ventana para seleccionar un nuevo servidor (NewServerWindow) con su correspondiente controlador (NewServerWindowController) que recibirá los eventos del usuario. Este aparece al ejecutar la extensión con la intención de seleccionar el servidor.
También se creará un controlador y una vista por defecto de la publicación. Esta vista estará compuesta por una vista de la publicación en forma de árbol y una vista que muestra los parámetros de los elementos seleccionados en el árbol. La idea es que este controlador y vista por defecto la puedan reutilizar los servidores que quieren mostrar los paneles navegando por un árbol.
Con la información que aporta infoProject más la que aporta el usuario para el servidor específico, el sistema ya es capaz de generar la configuración correspondiente.
Diseño
La extensión de publicación cuando arranca obtiene el controlador (IPublishController) del servidor seleccionado y que previamente registró en el registro de controladores (ControllersRegister). De este controlador obtiene una vista de publicación (IPublishView) que mostrará en la ventana principal. Previamente la extensión de publicación (realmente el controlador principal de extPublish MainController) ha obtenido una publicación desde la persistencia que se la pasa al controlador del servidor para que inicialice la vista con los elementos correspondientes. Cuando el usuario pulse el botón "publicar" de la ventana principal (MainWindow), el controlador principal obtendrá el modelo modificado por el usuario para invocar su método publish().
IPublishController
Interfaz que debe implementar cualquier controlador de servidor. La responsabilidades de un controlador son las de crear una vista del modelo (de Publication) y la de recibir los eventos del usuario realizados en la vista.
- Operaciones
getModel():Object Obtenemos el modelo que controla y que ha sido modificado por el usuario.
setModel(model:Object) Pasamos el modelo para que lo muestre en su vista
getView():PublishView Obtenemos la vista asociada al controlador
NOTA:Quizás sea mejor que devuelva un IPublishView
IPublishView
Interfaz que debe implementar cualquier vista de un modelo (publication,server, service, remoteResource).
Las responsabilidades de una vista de publicación son las de mostrar al usuario un estado del modelo.
- Operaciones
getModel():Object Obtenemos el modelo que muestra dicha vista.
setModel(model:Object) Pasamos el modelo para que lo muestre al usuario.
PublishView
JPanel que implementa IPublishView
PublishController
Quizás esta clase no haga falta
ControllerRegister
Registro de controladores. Cualquier plugin debe registrar aquí su controlador.
- Operaciones
addController(key_controller:String, clase:Class) Registramos el controlador identificado por la cadena "key_controller"
getControllersNames():Set Obtenemos los identificadores de todos los controladores registrados
getInstance(key_controller:String, clase:Class) Obtengo una instancia del controlador especificado
Contexto
La extensión de publicación debe ser capaz de generar los ficheros de configuración en varios servidores, por ello se ha empleado una arquitectura extensible mediante plugins.
En esta primera versión de la extensión, se han creado dos plugins, uno para el servidor Mapserver y otro para el servidor Geoserver.
publishMapserver
Contexto
La arquitectura del plugin utilizará el framework que define la extensión de publicación. Vemos entonces cómo el plugin emplea la parte de GUI y modelServers de la extensión de publicación para ampliar su funcionalidad al servidor específico de Mapserver.
Tenemos que el plugin está compuesto por una parte de interfaz gráfico y otra parte de librería que generará los ficheros de configuración de Mapserver.
Subsistemas
guiMapserver
Contexto
Para integrar la parte gráfica específica de Mapserver en la extensión de publicación, lo único que hay que hacer es crear el controlador correspondiente que ofrezca los paneles a la extensión de publicación.
Existe un controlador MapserverController del tipo PublishController que aportará las vistas de las entidades del modelo de Mapserver, por tanto tendremos una vista (del tipo PublishView) para el servidor, una para cada servicio y otra para cada recurso remoto asociado al servicio.
Mapserver WMS
En el caso del servicio WMS que ofrece Mapserver, se empleará el controlador por defecto que muestra en un árbol los servicios y los recursos remotos. Este controlador tendrá un tipo de vista por cada uno de los paneles.
libMapserver
Contexto
The new Gazetteer client offers the possibility to add new protocols. The idea is that anyone can add him own driver to manage its own protocol.
To add a new gazetteer protocol the first step is to create a new driver that just is a class that has to inherit of the IGazetteerServiceDriver interface. This interface forces to implement the common methods that all the Gazetteer drivers have to had. There is also an abstract class named AbstractGazetteerServiceDriver than implements this interface and it has a default implementation for some of the driver methods (but the using of this class is not necessary). Next figure shows the hierarchy:
The unique methods that are not implemented in AbstractGazetteerServiceDriver and they have to be implemented on every driver are:
GazetteerCapabilities getCapabilities(URI uri) throws Exception;
It sends a message to the server and tries to discover the server capabilities that will be used to create the queries.The driver has to discover if the server is ready an has to return a GazetteerCapabilities object.
GazetteerCapabilities is a class that has some methods that the user interface uses: the isAvailable() method is used to know if the server is ready (it return true by deafult). The getVersion() method indicates the protocol version that has the server and the getServerMessage() is used to show a message that is written on the user interface.
public Feature[] getFeature(URI uri, Query query) throws Exception;
This method sends a request to the server to search a geographic name. It has to return an array of Feature's that is an object that contains a name and a position. The SRS actually is specified by the service (not for every feature).
Query is an object that contains the information to make the query (the searched name, the thesaurus selection, the selected area...). This query has been filled with the fields that the user has selected in the user interface.
public String getServiceName();
It has to return the service name that will be showed in the user interface to select this driver.
To use a new driver, it has to be registered on the application using the GazetteerDriverRegister class. It implements the Singleton pattern: it has an static method to retrieve a register instance and this instance has a method to register the driver. Next fragment code can be used to register the ExampleNewDriver:
GazetteerDriverRegister register = GazetteerDriverRegister.getInstance();
register.register(new ExampleNewDriver());
The first step is to create a new JPanel that has to inherit from SearchAditionalPropertiesPanel. This class is a normal JPanel that has a method getProperties() that returns a Properties object. The created panel has to create a new Properties object and fill it with a set of key-value pairs where the key value will be used to retrieve the value that will be used to create the query.
This Properties object is retrieved from the panel and is added to the Query object that is a parameter in the getFeature() method. The driver can get it and used it to make its own requests.
Next step is to implement the next method in the driver:
public SearchAditionalPropertiesPanel getAditionalSearchPanel()
This method creates a SearchAditionalPropertiesPanel and return it. Every time that the driver that implements this method is selected on the connection window, this panel will be showed on the search window.
In the user interface there is a JTree that is used to make searches by cathegory. To fill this tree with a set of values it is necessary to fill the GazetteerCapabilities object returned in the getCapabilities method with a list of FeatureType Objects using the setFeatureTypes() method. A FeatureType object has basically a name, a description and a list of FeatureType objects that will be showed like children in the JTree.
La disponibilidad de metadatos de los datos es algo fundamental en el mundo de la Información Geográfica para el descubrimiento y acceso a los mismos. Sin la existencia de metadatos que identifiquen y definan los datos, aún existentes y disponibles, no es posible su localización por parte de usuarios externos. La producción de metadatos asociados a los datos es un trabajo laborioso que consume gran cantidad de recursos, por ello es una tarea que normalmente queda relevada a un segundo plano a pesar de su relevante importancia.
gvSIG no dispone en la actualidad de capacidad de gestión ni edición de metadatos, no siendo posible la publicación en Servicios de Catálogo (OpenGIS® Catalogue Service) de los datos que creamos o modificamos.
La extensión del Gestor de Metadatos pretende incorporar en gvSIG la capacidad de extracción semiautomática y escalada de metadatos de las fuentes de datos, haciendo que la tarea de documentación de los datos sea mucho más sencilla y eficiente. La extracción semiautomática de metadatos implica que el gestor sea capaz, directamente desde la fuente de datos y sin intervención del usuario, de extraer metadatos, que ofrecerán al usuario información de los datos sobre los que trabaja. Los metadatos que no puedan ser extraídos de forma automática podrán ser editados por el usuario y gestionados por el gestor de metadatos de forma que no sea necesario la intervención del usuario de forma reiterativa, si no que sean almacenados en plantillas e información adicional para posteriores usos.
Todo el desarrollo, tal y como gvSIG, se realizará bajo licencia GPL.
El trabajo consiste en desarrollar una nueva extensión
sobre la aplicación gvSIG, dotándolo de la funcionalidad de gestión de
metadatos, con el fin de obtener una
aplicación con la capacidad de extracción semiautomática de metadatos
implícitos, la gestión de los mismos y la posibilidad de publicación en un
servidor de catálogo.
Las principales
funcionalidades a desarrollar son:
- Extracción de metadatos de recursos en el sistema. Como primera fase los recursos a documentar estarán limitados a:
- las capas de una vista, en concreto capas vectoriales procedentes de fuentes de datos de ficheros shape.
- Metadatos inherentes al sistema y al usuario
- Gestión de metadatos de contactos del usuario
- Generación de un formato de metadato interno
- Edición de metadatos.
- Publicación de metadatos a un servidor de catálogo.
- MDML: formato interno en el que se trabaja.
- Metadato: Información asociada a un recurso cartográfico.
- Recurso cartográfico: información sobre una entidad georeferrenciada . Por ejemplo un mapa, una feature, una cobertura, un metadato,etc.
- Service: protocolo de comunicación en la arquitectura cliente-servidor.
- Server: producto software que implementa la especificación de un servicio.
- Publicación: generación de los recursos necesarios (ficheros de configuración) para que funcione correctamente un servidor.
- Feature: Entidad cartográfica vectorial como un punto, línea o polígono.
- Topónimo: nombre que identifica una feature.
- SRS: Sistema de referencia espacial. Sistema de coordenadas de cualquier georreferenciación.
- Leyenda: lista explicativa que define los símbolos utilizados en un mapa o gráfico.
La extensión de metadatos almacenará y mantendrá los
metadatos junto a los datos, adoptando la filosofía de que los metadatos forman
parte de los datos, o por lo menos se deben acompañar siempre.
Los metadatos se pueden clasificar en distintos niveles:
podemos hablar de metadatos asociados a la fuente de datos; metadatos asociados
a un conjunto de datos, como puede ser una vista compuesta por un diseñador de
cartografía para un uso específico; o de metadatos de una misma fuente de datos
en diferentes momentos.
En una primera fase de la implementación del Gestor de
Metadatos, vamos a extraer y mantener metadatos asociados a una capa de la
vista de gvSIG, que esta directamente relacionada con una fuente de datos. En
futuras fases de desarrollo se puede contemplar la documentación de otros
niveles de geodatos, como por ejemplo features, leyendas, proyectos, etc.
En una primera fase, el formato de los recursos de
los que se extraerán metadatos implícitos de manera semiautomática serán capas
de datos, en concreto capas vectoriales en formato SHP.
El gestor de metadatos
en su interacción con las clases base de gvSIG, extenderá el modelo
de objetos (fmap) dotando a los recursos identificados como posibles
objetos a metadatar con la capacidad de recuperar y almacenar
metadatos sobre si mismos. Estos recursos que en un principio se limitarán
a capas serán objetos tales como vistas, features, procesos,
etc.
Tres son los módulos que conforman la extensión: mdManager, mdPublish y mdExchange. A continuación, se muestran las interacciones existentes entre ellos.
mdManager
En este módulo encontramos la lógica y GUI's del editor de metadatos, así como del editor de contactos.
mdPublish
Se encarga de validar y publicar los metadatos, extraídos previamente, en un servicio de catálogo web.
mdExchange
Se encarga de realizar las tranformaciones, con ayuda de hojas de estilo XSLT, entre formatos XML de metadatos. Es utilizado por los módulos mdPublish y mdManager.
Las pruebas que se describen a continuación hacen referencia a los
test que intentan validar la correcta implementación de la
funcionalidad de la extensión, es decir, la correcta extracción, almacenado, edición y publicación de los metadatos de una capa vectorial. Por tanto, habrá un caso de prueba por cada
uno de los casos de uso de alto nivel que describíamos anteriormente.
Con estas pruebas conseguiremos validar la correcta integración de los
distintos subsistemas, exceptuando el gui que llevará un tratamiento especial en cuanto a pruebas.
Cuando entremos en detalle en cada subsistema o bloque,
especificaremos con más detalle las pruebas asociadas a cada bloque en
particular. En estos test de más bajo nivel se intentará validar la
funcionalidad específica de cada subsistema.
En esta primera versión de la extensión de metadatos, se va a
desarrollar la publicación solo para GeoNetwork (CS-W). Por tanto, y viendo los casos de uso de alto nivel,
los casos de prueba son los siguientes:
Caso de uso "Abrir capa sin metadatos existentes":
Puesto que para persistir los metadatos usamos ficheros locales, en el caso de abrir una capa que no posea metadatos anteriores deberemos generar de forma un fichero acorde al formato MDML que contenga los metadatos que se hayan podido extraer de forma automática. Por lo tanto este conjunto de pruebas se basarán en la existencia de dicho fichero y en su correcta estructura de acuerdo al formato interno MDML.
Caso de uso "Abrir capa con metadatos existentes":
En este caso nos encontramos que, al abrir la capa, ya existe un
fichero en formato MDML con metadatos sobre ella. Las pruebas
realizadas sobre este caso de uso consistirán en a partir del fichero generar un objeto Metadata que nos permita visualizarlo correctamente
en el editor de metadatos.
Caso de uso "Publicación en GeoNetwork":
En este conjunto de pruebas se deberá considerar los siguientes aspectos relacionados con los requisitos:
- Obtención del objeto Metadata asociado al Layer.
- Correcta conexión y validación contra el server.
- Transformación del MDML al formato standard destino.
Por tanto se espera que tras la ejecución de este test se ha creado una petición HTTP correcta de acuerdo con el server GeoNetwork, que nos permita incorporar los metadatos en dicho server.
Caso de uso "Importar metadatos":
Las pruebas realizadas sobre esta funcionalidad consistirán en importar un fichero XML con un formato soportado por la extensión. Para ello la extensión deberá realizar la transformación pertinente al formato MDML y posteriormente construir un objeto Metadata que nos permita visualizarlo correctamente
en el editor de metadatos.
Caso de uso "Exportar metadatos":
En este caso nos interesa almacenar en nuestra máquina los metadatos asociados a una capa, que estamos visualizando en el editor, en un fichero con el formato XML que elijamos. Para testear este caso de uso, debemos comprobar que los ficheros generados con la opción exportar son correctos de acuerdo al formato que hemos indicado.
Extracción automática de metadatos
La extracción automática de metadatos se realizará cada vez que el usuario realice las siguientes operaciones sobre una capa:
Realmente, nos referimos a extracción semiautomática porqué los metadatos serán obtenidos de diversas maneras:
- Extracción automática: directamente desde la fuente de datos y sin intervención del usuario, el gestor será capaz de extraer metadatos, que ofrecerán al usuario información de los datos sobre los que trabaja.
- Edición manual: metadatos que no pueden ser extraídos de forma automática o que, por requisito del usuario, podrán ser editados manualmente y gestionados por el gestor de metadatos de forma que no sea necesaria la intervención del usuario de forma reiterativa, si no que sean almacenados en plantillas para posteriores usos.
La extensión de metadatos almacenará y mantendrá los metadatos junto a los datos adoptando la filosofía de que los metadatos forman parte de los datos, o por lo menos se deben acompañar siempre.
En última instancia, el usuario puede publicar sus datos en un servido de Catálogo (por ejemplo, Geonetwork) o exportar los metadatos para visualizarlos mediante otro editos de metadatos (por ejemplo, CatMDEdit).
Módulo mdEditor
Una vez el usuario tiene una capa abierta (y por lo tanto sus metadatos asociados disponibles) tiene varias opciones a realizar con los metadatos:
Módulo mdExchange
El usuario no interacciona directamente con el módulo mdExchange así que, en los dos diagramas mostrados a continuación, los actores son el módulo gestor de metadatos y el módulo de publicación, respectivamente.
Diagrama de casos de uso con el módulo de publicación como actor
Módulo mdPublisher
Cuando el usuario selecciona la opción de publicar bien desde el editor de metadatos o desde el menú aparecerá una GUI que le permitirá seleccionar el origen de los metadatos (layer), el host dode deseamos publicar y las opciones de publicación de que dispone el host seleccionado. Y finalmente publicar los metadatos.
Motivación y objetivos
Se quiere dotar a gvSIG de un soporte de gestión de Metadatos como uno de los servicios principales de la aplicación. Sobre este soporte se basaría, tanto la gestión de los Metadatos geográficos típicos desde el punto de vista de publicación, como el uso de Metadatos para cualquier necesidad interna.
Este soporte en gvSIG puede suponer un hito importante, tanto a nivel de funcionalidad, como a nivel de impacto en el desarrollo, por lo que requiere un planteamiento de arquitectura específico que tenga en cuenta las dos líneas de gestión de Metadatos: los geográficos enfocados a la publicación y el uso interno.
El objetivo será desarrollar una arquitectura que permita cumplir los objetivos anteriores, y que tomará como base:
- Un planteamiento de arquitectura elaborado en distintas reuniones, en las que se definieron las necesidades generales de cara a gvSIG.
- Los requerimientos actuales de la extensión de Metadatos.
- Una revisión de definiciones, formatos estándares y documentación sobre Metadatos (Ver apartado de Referencias Externas).
Requerimientos previos
Tenemos unos requerimientos previos empleados como base para el desarrollo del prototipo de Metadatos,
A modo de resúmen, serían:
- Contemplar la extracción, consulta, edición, exportación e importación de Metadatos.
- Permitir la incorporación de formatos de Metadatos estándar a los que exportar o importar.
- Facilitar la usabilidad de la gestión de los Metadatos de cara al usuario.
- ¿Rendimiento/Escalabilidad/etc.?
Análisis de requerimientos
Análisis de Requerimientos
Requerimiento RQ1: Soporte de Metadatos en gvSIG
Se quiere incorporar a gvSIG el soporte de gestión de Metadatos asociados a cualquiera de los datos que maneja la aplicación. Este soporte debe ser un servicio del núcleo de gvSIG, que debe permitir la generación y consulta de Metadatos, tanto en cualquiera de los niveles de aplicación (subdriver, FMap y gvSIG), como en el Core y las distintas extensiones. Dicho soporte deberá contemplar la persistencia de los Metadatos y la posibilidad de consultar qué Metadatos está gestionando gvSIG.
Los Metadatos son datos sobre los datos (wikipedia) . Se emplean para describir datos individuales, contenidos o grupos de datos, y sirven para facilitar la comprensión, uso y gestión de los datos. Cada Metadato tendrá una serie de Atributos o Elementos, cuyos valores definen las características del dato al que se asocia el Metadato.
Al incorporar este requerimiento a gvSIG, cualquier componente que represente algún tipo de datos, podrá ser interrogado para obtener su Metadato. Dichos componentes serán marcados como Metadatables.
A nivel de modelo, un metadato será una entidad con una serie de atributos y tipos definibles de forma dinámica, con una jerarquía de datos similar a la que define el modelo de datos de gvSIG (proyecto -> documento -> capa -> ...). Así, la estructura de un Metadato se podrá definir de forma dinámica, y consultar los tipos de datos de sus Elementos.
Relación con el modelo de datos de gvSIG
A continuación se muestra un diagrama que tiene como objetivo detectar la relación que tienen los Metadatos con el modelo de datos conceptual de gvSIG Core. Se indican también algunos ejemplos de elementos de Metadatos que se pueden extraer en cada caso.
Como se puede ver, existe un paralelismo entre la estructura de datos de gvSIG y la estructura de los Metadatos. Se define una jerarquía en forma de árbol que empieza a nivel de la propia aplicación y va descendiendo por el árbol de elementos que maneja gvSIG.
Otras extensiones aportarán Metadatos de forma similar al core, ya que generalmente aportarán sus propios elementos dentro del modelo de gvSIG: otros tipos de capas, documentos, drivers, etc. Si las extensiones tienen Metadatos generales que no pertenecen a la estructura del Core, se pueden asociar a nivel de Aplicación, incluyendo un Metadato específico para cada extensión.
De este requerimiento surgen los subrequerimientos RQ1.1 y RQ1.2.
Diagrama de entidades
Diagrama de entidades
A partir del análisis de los requisitos se definen una serie de entidades para la gestión de los Metadatos en gvSIG. A continuación se muestra un diagrama con dichas entidades y su interacción:
Diagrama de contexto
Diagrama de contexto
A continuación se muestra un diagrama que muestra las entidades definidas en el apartado anterior, dentro del contexto de gvSIG.
Las entidades principales de gestión de Metadatos, formarán parte del propio core de gvSIG, al tratarse de un servicio central de la aplicación.
La extensión de Metadatos se encargará de gestionar la parte de presentación de los Metadatos, incluyendo la visualización y edición de Metadatos, así como la visualización y edición sobre plantillas de formatos de Metadatos estándar.
Subsistemas
Metadatable
Descripción
Todo componente de gvSIG al que se pueden asociar Metadatos. Por ej: Proyecto, Documento, Capa, etc.
Responsabilidades:
- Generar los Metadatos de sus datos.
- Persistir sus Metadatos.
- Permitir la consulta de sus Metadatos.
MDManager
Introducción
Se encarga de la creación y serialización de Metadatos, acorde a su definición.
Responsabilidades:
- Crear un Metadato a partir de su definición.
- Serializar un Metadato a XML para ser incluido en la persistencia de un proyecto.
- Recrear un Metadato a partir del XML de la persistencia de proyecto.
MDRegistry
Introducción
El registro de Metadatos se encarga de gestionar la lista de definiciones de Metadatos.
Una definición de Metadatos especifica el formato de un Metadato para un elemento de gvSIG (proyecto, documento, capa, etc.). Es decir, indica qué elementos va a contener el Metadatos, y sus tipos de datos.
Por otro lado, gestionará también la asociación entre sinónimos y las definiciones de Metadatos. Los sinónimos especifican una relación entre un Metadato en gvSIG y el mismo Metadato en otro formato de Metadatos externo.
Responsabilidades:
- Registrar definiciones de Metadato, Elementos y tipos de datos.
- Permitir la consulta de definiciones de Metadato.
- Asociar mediante sinónimos, Elementos de Metadatos con su equivalente en formatos de Metadato estándar.
MDEditor
Introducción
Se encarga de la edición de Metadatos de usuario, es decir, aquellos que no pueden ser generados a partir de los datos.
Responsabilidades:
- Incluir en el interfaz de usuario de gvSIG pantallas de visualización y edición de los Metadatos.
- Permitir la visualización y edición de Metadatos, con plantillas de formatos de Metadatos estándar.
Extracción de Metadatos
Introducción
La extracción de Metadatos no es realmente un subsistema independiente, sino que será un proceso que formará parte de cada uno de los Drivers o Datasource de gvSIG. De la misma forma que son capaces de cargar los datos para una capa, deberán extraer los Metadatos correspondientes.
Persistencia de los Metadatos
Introducción
La persistencia de los Metadatos deberá ser capaz de cubrir las siguientes necesidades:
- Creación y actualización de datos con una estructura jerárquica.
- Consulta de datos, según su posición jerárquica, y a través de consultas según alguno de sus datos.
- Facilitar la importación y exportación de parte o todos los Metadatos.
- Permitir el registro a eventos ante cambios en determinados Metadatos.
Sería interesante que soportara también transacciones en la modificación de los Metadatos.
Implementación
Describir cómo serían los procesos de:
- Captura de Metadatos: a través de listeners siempre que sea posible, o desde la implementación de los módulos que correspondan.
- Repositorio de metadatos: consulta y escritura de metadatos, tipos, sinónimos, etc.
- Importación y exportación a formatos estándar.
Proponer y justificar el uso de un JCR, como por ejemplo Apache Jackrabbit. Ver funcionalidad que aporta y como encaja en las necesidades.
Modelo de datos
- Estudiar relación entre metadatos, la configuración y las preferencias de usuario que se guardan actualmente en gvSIG. Serian necesarias las segundas?
- El diccionario contendrá el catálogo de tipos de Metadatos. Proveerá un interfaz que permita interrogar por la lista de tipos de metadatos disponibles, así como incluir tipos. El core de gvSIG incluirá los requeridos por los distintos formatos estándares de Metadatos, y cada extensión o componente podrá incluir los suyos propios. ¿Permitir sólo Metadatos de los que exista una definición en el diccionario?
- Como opción de reutilización, puede ofrecer un interfaz de exportación/importación en formato XML MDML. Así se pueden reutilizar los Templates actuales, con su XSL de conversión a/desde su formato estándar.
Los templates podrían ser grupos de extensiones de entrada, salida, conversión, etc.
Buscar otro nombre, ya que no se corresponden con, por ejemplo, los Templates de GeoNetwork.
- Cada proyecto o extensión debería publicar en algún lugar la lista de Metadatos que proporciona. ¿Deberían ser validados por gvSIG?
- Implementar el modelo de Metadatos sobre EMF?
- Establecer un mecanismo para establecer la relación jerárquica entre los Metadatos.
LibUI es una libería de soporte, independiente de gvSIG, que proporciona a los
desarrolladores una serie de controles, paneles y otras herramientas que pueden resultar
de utilidad en un momento determinado.
En este texto vamos a detallar todos los componentes LibUI que aparecen con la finalidad
de proporcionar una herramienta de consulta útil para desarrolladores. Mostraremos una
breve descripción, capturas de su utilización y un sencillo ejemplo de ejecución de cada una de estas herramientas.
Las herramientas disponibles en la librería son:
Es la clase sobreescrita JButton para utilizar por los desarrolladores de gvSIG con el fin de aunar criterios a la hora de diseñar los botones a utilizar en la aplicación. Lo que proporciona esta clase es el tamaño estándar de los botones, que no deben ser más pequeños que el tamaño establecido. Si por necesdidad del desarrolloador fuera necesario etiquetar el botón con más caracteres de los que caben en el tamaño estándar del botón, la clase evitará utilizar los carácteres "..." y lo que hará será aumentar el ancho del botón en el tamaño mínimo necesario.
A continuación mostramos un ejemplo de utilización.
Al ser una extensión de JButton con la particularidad del tamaño establecido, la forma de utilizarlo es la misma que la del JButton original
JButton b2= new JButton("Botón"); //Constructor
content.addComponent(b2); //Lo añadimos al panel
Esta clase extiende de JPanel y lo que hace es crear un JPanel con un GridBagLayout que permite añadir facilmente nuevos componentes en filas que son automáticamente insertadas y alineadas en el panel usando los métodos apropiados que se proporcionan.
Además, se propociona la clase JBlank que lo que hace es crear un panel en blanco con la finalidad de que sea utilizado como separador entre filas de componentes añadidos en el Layout.
A continuación se muestra unas capturas de la utilización de esta clase.
El código empleado es el siguiente.
Creamos un objeto de la clase GridBagLayoutPanel y actualizamos el tamaño que queremos
GridBagLayoutPanel content= new GridBagLayoutPanel();
content.setPreferredSize(new Dimension(300,200));
Creamos 4 botones
JButton b1 = new JButton("Prueba1");
JButton b2 = new JButton("Prueba2");
JButton b3 = new JButton("Prueba3");
JButton b4 = new JButton("Prueba4");
Añadimos tres componentes en una fila, ajustando los tamaños al ancho del panel
content.addComponent(b1,b2,b3, 1);
Añadimos Blank?s para separar
content.addBlank();
content.addBlank();
content.addBlank();
content.addBlank();
content.addBlank();
content.addBlank();
content.addBlank();
Y por último añadimos el último botón que también ocupe todo la fila con el Inset indicado
content.addComponent(b4,1,new Insets(1,0,0,1));
Esta clase crea un menú PopUp para poder incorporarlo a elementos de edición de texto como es JTextField por ejemplo. El menú que se crea posee los siguientes botones:
- edit undo
- edit redo
- edit cut
- edit copy
- edit paste
- edit delete
- edit selectall
Se incorpora herramientas para modificar la visisbilidad de las diferentes acciones que se pueden realizar desde el PopUp según desee el usuario.
La clase deja en manos del desarrollador la implentación de las acciones que realizarán los distintos botones del PopUp, así como la acción que lo activará (normalmente al pulsar el botón derecho del ratón sobre un campo de edición de texto).
A continuación se muestra una imagen del menú PopUp que se crea utilizando esta clase.
El código necesario para utilizar la clase es el siguiente. Se observa el constructor y el evento necesario para que aparezca el menú cuando se pulsa el tercer botón del ratón
MouseAdapter editorMouseListener = new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
JOptionsEditionByMousePopupMenu panel= new JOptionsEditionByMousePopupMenu();
Logger logger = Logger.getLogger(JTextField.class.getClass());
logger.debug("MOUSE-LISTENER : MOUSE-mouseClicked!" + e.getButton());
logger.debug("MOUSE-LISTENER : MOUSE-mouseClicked!" +e.isPopupTrigger());
if (e.getButton() == MouseEvent.BUTTON3) {
// Enable the "Select-All" option (by default it's always enabled)
panel.setEnabledSelectAllOption(true);
panel.setLocation((int)text.getLocationOnScreen().getX() + e.getX(), (int)text.getLocationOnScreen().getY() + e.getY());
panel.setInvoker(text);
panel.setVisible(true);
}
}
};
NumberRange extiende de Panel. Esta clase proporciona un campo de texto y dos botones. El funcionamiento es el siguiente, la clase crea un JNumberText cuyo valor se podrá incrementar o decrementar mediante dos botones (JButton). Mediante el constructor podremos asignarle el valor inicial y final, así como el valor con el que queremos que se inicialice el campo de texto.Además también podemos indicarle mediante el constructor el número de dígitos que queremos mostrar en el campo. Lo podemos ver en las siguientes imágenes.
En la imagen el "Number Range" se ha creado con los parámetros: dígitos mostrados 2; valor inicial 0;
valor mínimo -5 y valor máximo 5.
En las siguientes líneas de código se muestra el constructor y como añadir el componente al panel. En el constructor se incluyen el tamaño del campo, el valor inicial que se muestra en TextField y los valores máximo y mínimo que podrá alcanzar el número del campo. La segunda línea es la función que permite insertar el componente NumberRange en un JPanel ya creado
ejamplo_numberRange a = new ejamplo_numberRange(2,0,-5,5);
c.add(a);
Esta clase crea un campo de texto y dos botones para aumentar el o disminuir el valor que aparece en el campo de texto. Están implementados listeners para que cuando se pulsa el botón más o el botón menos se ejecute la acción correspondiente pero no se gestiona que el valor que se introduzca a mano sea de un tipo determinado, esto debe ser tenido en cuenta por el desarrollador.
Captura de su uso.
Código generado para implementar el ejemplo.
Constructor, los parámetros que se le pasan son los siguientes, ancho del campo de texto, minimo valor, máximo valor, valor por defecto que aparecerá en el cuadro de texto y por último "true" si queremos que los botones aparezcan a la derecha del texto o "false" si queremos que salgan a la izquierda
TextIncreaserContainer tic1 = new TextIncreaserContainer(70, 0, 10, 0, true);
Modificamos el valor a mostrar en el campo de texto
Y por último añadimos el panel al frame
frame.getContentPane().add(tic1);
Esta clase extiende JEditorPane, y permite guardar en una cadena el formato elegido para el texto que se quiera mostrar. Se elige el formato de la cadena de texto que se desea mostrar, es decir, la fuente, si negrita o cursiva, etc. La clase modificará en la cadena que se guarda los cambio realizados al cambiar la fuente.
Se muestra un ejemplo de como mostraría la cadena "Esto es una prueba" con formato de fuente texto plano.
Y otro de como mostraría la misma cadena con formato ITALIC.
El código necesario para generar estos ejemplos se detalla a continuación.
Constructor para crear el objeto
JTextPreview txt= new JTextPreview();
Creamos una fuente de texto con el siguiente formato, en negrita y con tamaño de texto 20, lo asignamos al EditorPane, y establecemos el texto a mostrar
Font font= new Font("Serif", Font.BOLD,20);
txt.setFont(font);
txt.setText("Esto es una prueba.");
Por último lo asignamos al panel
JDecimalField extiende la clase IoSTextField y permite la entrada de valores en formato BigDecimal.
Esta clase está relacionada con BigDecimalFormat y se utiliza para crear un campo de texto con este formato
de datos. La clase internamente traducirá la cadena de texto introducida en el TextField en un objeto de la clase
BigDecimal que pueda ser utilizado por el desarrollador.
A continuación un ejemplo de uso de la clase.
A continuación se explica como se ha utilizado la clase para obtener este cuadro de texto.
Esta línea permite la creación de un objeto JDecimalField con 4 columnas, con autoformato y manteniendo el foco con la entrada de algún valor no válido
JDecimalField test =new JDecimalField(4,true,true);
Esta línea de código permite establecer una escala, es decir, cuantos decimales se mostrarán como mínimo y como máximo. En el caso que el número a introducir tenga menos decimales que el valor mínimo de escala se completará con ceros a la izquierda. Si existen más decimales que el valor máximo de la escala se aplicará un redondeo para ajustar a la escala
Con este fragmento de código creamos el número en formato BigDecimal que queremos incluir y lo mostramos en el TextField
BigDecimal bd= new BigDecimal("2.01234567890123456789");
test.setValue(bd);
Apéndice.
Otras clases de interés:
- "IoSTextField" es una clase abstracta que extiende JTextField y que tiene una serie de particularidades. En lugar de mostrar la última parte escrita muestra la parte delantera del texto (leading text) introducido en el TextField. Controla la ganancia y pérdida de foco por parte del TextField. Permite formateo de texto y la posibilidad de mantener el foco aunque el valor introducido sea no válido (en relación con el formato de texto introducido, si se ha optado por introducir un formato de texto).
- "BigDecimalFormat" es una clase que extiende de DecimalFormat y se utiliza para formatear números en formato BigDecimal, es decir, la clase permite utilizar un patrón establecido por el desarrollador con el formato que desee para el número BigDecimal. También se aplicará un redondeo, seleccionado por el usuario, en el caso que sea necesario. El formato BigDecimal surge como solución al problema aparecido con el tipo de datos Double ya que éste no puede representar números del tipo "0.01" de forma precisa ya que el número representa "1/100" que en binario es periódico, luego para representarlo se trunca o redondean valores con lo que se pierde precisión y a la hora de operar con estos valores se incurre en un error y no se obtendrá el valor esperado. BigDecimal soluciona este problema.
- "JNumberField" es una clase que extiende de IoSTextField y permite la entrada de valores numéricos. Los tipos numéricos soportados son BigInteger, Integer y Long. La clase la puede utilizar el desarrollador para introducir valores en los formatos antes mencionados mediante un campo de texto.
DataInputContainer
Esta clase extiende de JPanel e implementa FocusListener y KeyListener. El objetivo es crear un panel con un JTextField que detecte que el tipo de carácteres introducidos sea numérico o alfanumérico. Para ello implementa una serie de métodos para controlarlo. Existe un método, como se verá en el ejemplo, que permite cambiar de un formato de carácter permitido a otro. La clase incorpora una etiqueta asociada al campo de texto que por defecto es "JLabel:".
A cotinuación se muestra una imagen del campo de texto.
Un ejemplo de utilización de la clase es el siguiente.
Creamos el objeto DataInputContainer
DataInputContainer data = new DataInputContainer();
Establecemos que solo acepte carácteres numéricos
Añadimos la etiqueta y establecemos el tamaño que deseamos que tenga el TextField
data.setLabelText("Prueba");
data.setPreferredSize(new Dimension(100,25));
Añadimos el TextField al frame creado para el ejemplo
frame.getContentPane().add(p);
CoordDataInputContainer
Una extensión de esta clase es "CoordDataInputContainer" que consta de cuatro DataInputContainer en un panel con disposición BordeLayout. La clase contiene los métodos para manipulación de los valores que se introducen (set y get). Al ser cuatro DataInputContainer se puede establecer si deseamos que sean carácteres numéricos o alfanuméricos. Además permite etiquetar los componentes de dos en dos mediante unos iconos. Existe un método que permite etiquetar los TextFields, el "border" que incluye y dos etiquetas identificativas para cada par de componentes. A continuación se expone un ejemplo.
Se incluye una imagen de muestra.
El código necesario para crear esta clase es el siguiente.
Constructor
CoordDataInputContainer coord = new CoordDataInputContainer();
Establecemos los parámetros del panel, nombre para el "border", para cada componente y para cada par de componentes
coord.setParameters("Coordenadas pixel", "Sup.Izq", "X","Y","Inf.Der","X","Y");
Establecemos que tipo de valores queremos que acepten los campos de texto
coord.setDecimalValues(true);
coord.setCaracterValues(false);
Por último, lo añadimos al frame
frame.getContentPane().add(coord);
Descripción
Esta clase crea un ComboBox con fechas. Cuando hacemos clic en el desplegable del ComboBox creará un mensaje popup con un calendario en el que podremos seleccionar la fecha deseada. A continuación se ocultará el popup y la fecha seleccionada aparecerá en el recuadro del ComboBox. El formato en el que se mostrará la fecha es el siguiente:
mes día, año (Ejemplo: jul 10, 2007).
Dentro del "popup" nos encontramos con un calendario mensual en el que aparecen dos tipos de navegadores representados por "<" ">" que se utilizan para cambiar de mes y "<<" ">>" que se utilizan para cambiar de año.
Imágenes
A continuación se mostrará el código indicativo de como usar la clase.
// En esta línea de código mostramos la llamada al constructor
DateComboBox dcb = new DateComboBox();
// Modificamos la propiedad para que nos permita editarlo
dcb.setEditable(true);
// Y lo añadimos al panel (JPanel), igual que un ComboBox
c.add(dcb); (c es el panel)
Estas dos clases extienden JPanel. Crean un panel para incluirlo en otros paneles.
En el panel que crea la clase JCalendarCDatePanel se inserta un ComboBox que al desplegarlo aparece un mensaje PopUp con un calendario para que el usuario seleccione la fecha que desee. La fecha seleccionada aparecerá en el campo del ComboBox. La limitación de esta clase es que el mensaje PopUp que aparece no se puede desplazar ni redimensionar a voluntad del usuario.
Este problema se ha corregido en la clase JCalendarDatePanel. Versión similar a JCalendarCDatePanel pero se soluciona la limitación de ésta usando internamente un JButton y un JDialog que contiene un CalendarDateDialog. Así el usuario por defecto ve la fecha sobre el JButton, y si lo pulsa aparece el JDialog, sobre el que puede seleccionar la fecha deseada y a su vez puede desplazar el componente donde más desea por la pantalla. Una vez seleccionada la fecha, el JDialog deja de estar visible y en el JButton se puede ver la nueva fecha.
A continuación se incorporan ejemplos de las dos clases implementadas.
El código necesario para generar los ejemplos ha sido el siguiente
JCalendarCDatePanel t = new JCalendarCDatePanel();
JCalendarDatePanel t = new JCalendarDatePanel();
// Y posteriormente incluirlos en el panel
content.add(t);
La clase extiende JDialog. Esta clase implementa en un diálogo un calendario para que pueda ser utilizado por otros componentes gráficos. En el ejemplo siguiente se ha utilizado para la implementación un JButton que lo invoca mediante la implementación de un listener para recoger cuando se pulsa un botón del ratón sobre él.
El diálogo que se crea se puede dsplazar y redimensionar a voluntad del usuario.
A continuación se muestran imágenes con el ejemplo de la clase.
El código necesario es el siguiente:
Constructor de la clase
JCalendarDateDialog t=new JCalendarDateDialog();
Creamos un botón con un listener que al ser pulsado invoca al Dialog del calendario y lo añadimos al panel
JButton jButton = new JButton();
jButton.setPreferredSize(new Dimension(widthJB, heightJB));
jButton.setText(Messages.getText("date"));
jButton.addMouseListener(new MouseAdapter() {
public void mouseClicked(MouseEvent e) {
t.setVisible(true);
t.setLocationRelativeTo(null);
System.out.println("Selected date: " + new SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()).format(t.getDate()));
}
}
);
jF.getContentPane().add(jButton);
Esta clase es una extensión de DefaultComboBox y permite realizar búsquedas de objetos y mostrarlos en un ComboBox. El objeto devuelto depende de la configuracón que utilicemos y de la búsqueda que realicemos. El modelo que representa esta clase puede ser configurado con tres atributos:
Comportamiento inicial (Start Behavior): Configura como devolverá los items al principio (siempre devuelve todos lo items):
- MAINTAIN_ORIGINAL_POSITION_START: Devuelve los items en el orden en el que se habían introducido.
- ORDERED_START: Devuelve los items ordenados.
- DISORDERED_START: Devuelve los items desordenados.
Comportamiento de búsqueda (Search Behavior): Configura cómo devolverá un objeto al realizar una búsqueda:
- MAINTAIN_ORIGINAL_POSITION_ALL_ITEMS_SEARCH: Devuelve todos los objetos en el orden en que han sido introducidos.
- ORDERED_ALL_ITEMS_SEARCH: Devuelve todos los items ordenados.
- DISORDERED_ALL_ITEMS_SEARCH: Devuelve todos los objetos desordenados.
- MAINTAIN_ORIGINAL_POSITION_DYNAMIC_SEARCH: Devuelve los items cuyo valor de cadena comienza por una determinada serie de carácteres indicados. Los objetos son devueltos en el orden en el que han sido introducidos.
- ORDERED_DYNAMIC_SEARCH: Realiza una búsqueda similar a la configuración anterior pero en este caso los items devueltos son ordenados.
- DISORDERED_DYNAMIC_SEARCH: Realiza una búsqueda similar a las anteriores pero en esta configuración los items son devueltos de forma desordenado
Distinguir entre mayúsculas (Case Sensitive): Distingue entre mayúsculas y minúsculas a la hora de realizar la búsqueda:
- TRUE: Distingue entre mayúsculas y minúsculas cuando se realiza una búsqueda.
- FALSE: No distingue entre mayúsculas y minúsculas cuando se realiza la búsqueda.
Por defecto la clase está configurada de la siguiente manera:
- Start_Behavior = MAINTAIN_ORIGINAL_POSITION_START
- Search_Behavior = ORDERED_DYNAMIC_SEARCH
- Case_Sensitive = FALSE
El árbol de herencia se completa con "ComboBoxItemSeekerConfigurableModel" y con "ComboBoxSingularItemsSeekerConfigurableModel" que heredan de la clase anterior. La diferencia entre ambas es que la segunda esta optimizada para realizar búsquedas cuando no aparecen objetos repetidos, en cambio no funciona correctamente si aparecen items duplicados. En este caso utilizar la primera, aunque el tiempo empleado sea mayor.
En las siguientes imágenes vemos un ejemplo de su utilización:
En esta primera imagen se muestran todos los objetos introducidos según una configuración inicial.
En la segunda imágen se muestran los objetos ordenados y que comiencen por el carácter "a".
En la tercera imágen aparecen los objetos de la lista anterior que comienzan por "aa".
Los objetos que aparecen lo hacen según las configuraciones introducidas en el programa de test. Además incorpora un menú PopUp que se crea cuando hacemos clic con el botón derecho del ratón sobre el campo de edición del ComboBox. Este menú posee las acciones típicas de edición de texto como son copiar, pegar, cortar, etc. Para más información sobre el menú PopUp mencionado visitar la página JOptionsEditionByMousePopupMenu de esta misma sección.
A continuación se muestra los fragmentos de código de interés en la realización del programa de test.
El constructor de la clase
JComboBoxItemsSeekerConfigurable jCBSC = new JComboBoxItemsSeekerConfigurable();
La forma de incluir algunos items para realizar las pruebas de la utilización
jCBSD.addItem(new Item("libNomenclatorIGN_GUI"));
jCBSD.addItem(new Item("libRemoteServices"));
jCBSD.addItem(new Item("extNomenclatorIGN")); // Duplication test
jCBSD.addItem(new Item("libUI"));
jCBSD.addItem(new Item("libUI"));
jCBSD.addItem(new Item("a"));
jCBSD.addItem(new Item("aa"));
jCBSD.addItem(new Item("aaa"));
jCBSD.addItem(new Item("aaa"));
Diferentes líneas para activar o desactivar flags para realizar pruebas con los items anteriores
// Test a configuration of behavior of the search of JComboBoxSearcheableDynamic
//jCBSD.setSearchBehavior(AbstractDefaultComboBoxItemsSeekerConfigurableModel.ORDERED_ALL_ITEMS_SEARCH);
//jCBSD.setSearchBehavior(AbstractDefaultComboBoxItemsSeekerConfigurableModel.DISORDERED_ALL_ITEMS_SEARCH);
//jCBSD.setSearchBehavior(AbstractDefaultComboBoxItemsSeekerConfigurableModel.ORDERED_DYNAMIC_SEARCH);
//jCBSD.setSearchBehavior(AbstractDefaultComboBoxItemsSeekerConfigurableModel.DISORDERED_DYNAMIC_SEARCH);
//jCBSD.setStartBehavior(AbstractDefaultComboBoxItemsSeekerConfigurableModel.ORDERED_START);
...
Cambio de flags de control/vista
// Test the change of the flag 'onlyOneColor_Flag'
jCBSD.setOnlyOneColorOnText_Flag(true);
// Test the change of the flag 'beepEnabled_Flag'
jCBSD.setBeepEnabled_Flag(false);
// Test the change of the flag 'hidePopupIfThereAreNoItems_Flag'
jCBSD.setHidePopupIfThereAreNoItems_Flag(false);
// Test the change of the flag 'toForceSelectAnItem_Flag'
jCBSD.setToForceSelectAnItem_Flag(false);
Cambio de flags del modelo
DefaultComboBoxItemsSeekerConfigurableModel model = (DefaultComboBoxItemsSeekerConfigurableModel)
jCBSD.getModel();
// Test the change of the flag 'itemsShownInListBox_Flag'
model.setLanguageRules_Flag("en_US");
// model.setLanguageRules_Flag("fr_FR");
// model.setLanguageRules_Flag("es_ES");
// Test the change of the flag 'caseSensitive_Flag'
model.setCaseSensitive_Flag(DefaultComboBoxItemsSeekerConfigurableModel.CASE_INSENSITIVE);
// model.setCaseSensitive_Flag(false);
// Test the change of the flag 'itemsOrder_Flag'
model.setItemsOrder_Flag(DefaultComboBoxItemsSeekerConfigurableModel.MAINTAIN_POSITION);
// model.setItemsOrder_Flag(DefaultComboBoxItemsSeekerConfigurableModel.DISORDERED);
// model.setItemsOrder_Flag(DefaultComboBoxItemsSeekerConfigurableModel.ALPHABETICAL_ORDERED);
// Test the change of the flag 'itemsShownInListBox_Flag'
model.setShowAllItemsInListBox_Flag(DefaultComboBoxItemsSeekerConfigurableModel.SHOW_ALL_ITEMS);
// model.setShowAllItemsInListBox_Flag(true);
// model.setShowAllItemsInListBox_Flag(DefaultComboBoxItemsSeekerConfigurableModel.SHOW_ONLY_MATCHES);
// model.setShowAllItemsInListBox_Flag(false);
Es una clase que extiende de JPanel. La clase implementa un ComboBox con los posibles valores de escala, además escucha un evento para mostrar en primera instancia el valor actual de las escala, es decir, el primer valor que muestra en el campo del ComboBox es el valor de la escala actual de la imágen. Este valor es capturado mediante un listener.
Ejemplo de ComboScale lo encontramos en la ventana principal de gvSIG cuando tenemos cargada una capa y se utiliza para mostrar la escala a la que se muestra la imagen. Cuando seleccionamos un valor de escala en el ComboBox se redibuja la imágen con el nuevo valor.
Se adjuntan imágenes tanto de la utilización del ComboScale en una aplicación independiente de test y su uso dentro de gvSIG.
A continuación se muestran fragmentos de código de cómo utilizar esta aplicación
Constructor de la clase
ComboScale cs = new ComboScale();
Creamos un vector con los elementos que deseamos tenga la escala y lo incluimos en el ComboScale
long[] scale=new long[5];
scale[0]=100;
scale[1]=500;
scale[2]=1000;
scale[3]=5000;
scale[4]=10000;
cs.setItems(scale);
Establecemos el valor que queremos que muestre el ComboScale en su campo de texto
Añadimos el ComboScale al panel
frame.getContentPane().add(cs);
Se ha añadido un botón adicional que establecerá el valor que mostrará el ComboScale en "500" cuando sea pulsado. El código necesario para realizar esa acción es el siguiente. Primero el contructor del botón ,luego las acciones que realizará y por último el código para incluirlo en el panel
JButton btn = new JButton(); //Creamos el botón
btn.addActionListener(new ActionListener(){ //Listener que espera la acción de pulsar el botón
public void actionPerformed(java.awt.event.ActionEvent e) {
cs.setScale(500); //Establece 500 como valor a mostrar en el campo de escala
}
}
);
frame.getContentPane().add(btn); //añadimos el botón al panel
Esta clase extiende de JPanel. El objetivo de esta clase es implementar una herramienta que permita desplegar un ComboBox y que los items que aparezacan sean botones capaces de realizar acciones que previamente se les haya asignado. Una vez pulsado el botón deseado se mostrará en el ComboButton la imágen asociada a ese botón (si ha sido asociado por el desarrollador) o el texto informativo de la acción que implenta el botón.
En la siguientes imágenes se muestra un ejemplo de la utilización de esta clase.
A continuación se incluyen el código explicativo de como utilizar esta clase.
El constructor
ComboButton cb=new ComboButton();
Se crean dos iconos para mostrar con los botones del ComboButton
ImageIcon icon1=new ImageIcon("images/backward.png");
ImageIcon icon2=new ImageIcon("images/forward.png");
Los constructores de los dos botones que incluye el ComboButton. A los constructores se les llama con los iconos creados
JButton b1=new JButton(icon1);
JButton b2=new JButton(icon2);
Las acciones que realizarán los botones cuando sean pulsados
b1.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
System.out.println("Pulsando el botón 1");
System.out.println("Action command: "+arg0.getActionCommand());
}
}
);
b1.setActionCommand("action1");
b2.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent arg0) {
System.out.println("Pulsando el botón 2");
System.out.println("Action command: "+arg0.getActionCommand());
}
}
);
b2.setActionCommand("action2");
Esta clase extiende JList e implementa las interfaces necesarias para usar Drag and Drop con listas. Drag and Drop es una herramienta útil que permite seleccionar y arrastrar elementos. Al extender de un JList lo que se consigue es implementar esta funcionalidad en listas, permitiendo al usuario cambiar el orden de los elementos de la lista arrastarando y soltando, añadir elementos de otras JDnDList arrastrando elementos con el ratón, además de otras funcionalidades.
Se ha implementado JDnDListModel que añade la funcionalidad típica de listas para poder utilizarla con Drag and Drop.
Se ha implementado un programa de test donde aparecen dos listas. En las imágenes veremos como se puede ir cambiando el orden de los elementos arrastrando y soltando y como podemos añadir elementos de una lista a otra de la misma manera.
A continuación se incluyen fragmentos de código ilustrativos de como utilizar estas clases.
Primero creamos el modelo de listas que vamos a incluir en el panel con los elementos que creamos oportunos
JDnDListModel listModel = new JDnDListModel();
listModel.addElement("primero");
listModel.addElement("segundo");
listModel.addElement("tercero");
JDnDListModel listModel2 = new JDnDListModel();
listModel2.addElement("cuarto");
listModel2.addElement("quinto");
listModel2.addElement("sexto");
Después invocamos al constructor de la clase JDnDList con los modelos creados en el punto anterior
JDnDList list = new JDnDList();
list.setModel(listModel);
JDnDList list2 = new JDnDList();
list2.setModel(listModel2);
Y por último sólo nos queda incluir las dos listas con funcionalidad Drag and Drop en el panel
content.add(list);
content.add(list2);
MultiLineToolTip es una clase que hereda de JToolTip. El objetivo de esta clase es permitir al desarrollador
poder utilizar Tips (mensajes que aparecen cuando situamos durante unos segundos el ratón sobre un componente) de
más de una línea. Por defecto el componente JToolTip solo permite estructurar los Tips en una sola línea, con esta clase podemos crear Tips de varias líneas. Cuando se crea el texto del ToolTip habrá que indicar el final de una línea mediante los caracteres "\" (símbolo de final de línea).
Se puede ver el resultado de la utilización de esta clase en la siguiente imágen.
A continuación se incluye el código necesario para utilizar esta herramienta.
Creamos un botón con la posibilidad de utilizar ToolTips de más de una línea
JButton button = new JButton ("Prueba") {
public JToolTip createToolTip() {
MultiLineToolTip tip = new MultiLineToolTip();
tip.setComponent(this);
return tip;
}
};
Ponemos el fragmento de texto que queramos como ToolTip, separado en líneas, sobre el botón y lo añadimos al panel
button.setToolTipText("Botón de prueba \n no ejecuta \n ninguna acción \n (Ejemplo MultiLineToolTip).");
content.add(button);
Este control crea un panel con un conjunto de botones. La utilidad de esta clase es la posibilidad de incluirlos
en alguna aplicación más grande que requiera de un panel con estas características.
En la siguiente imágen se muestra el panel que se crea con esta clase.
El código para crear el panel es
FilterButtonsJPanel panel=new FilterButtonsJPanel();
content.add(panel);
Esta clase establece una herramienta para implentar filtros. Se proporciona un campo de texto a la izquierda y otro a la derecha de una serie de botones para operar entre los valores que aparezcan en ellos. Además se muestra debajo de los botones un campo donde aparece una leyenda de las operaciones que se van a aplicar.
A continuación se muestran imágenes de su utilización en un programa de prueba como dentro de gvSIG.
La clase TableFilterQueryJPanel amplía la funcionalidad de esta clase añadiéndole unos botones cuaya funcionalidad la definirá el usuario. Esta clase no se utiliza dentro de la aplicación gvSIG.
El código necesario para crear el panel es el siguiente
Constructor para crear el panel
FilterQueryJPanel filterQueryJPanel = new FilterQueryJPanel();
Conjunto de clases abstractas e interfaces que se usan para la creación de árboles de opciones en extensiones de gvSIG. Un árbol de opciones es un tipo de menú en el que existen nodos padres e hijos. El origen es un nodo raíz, estableciéndose de este modo una jerarquía con las propiedades típicas de los árboles como estructuras de datos que se hayan querido implementar (no pueden existir ciclos, por ejemplo). El árbol está incluido en un panel donde se muestra.
El conjunto de clases, clases abstractas e interfaces que forman parte de este paquete de herramientas para desarrolladores son:
- AbstractCellEditor.
- TreeTableCellEditor.
- TreeTable.
- TreeTableModelAdapater.
- TreeTableCellRenderer.
- AbstractTreeTableModel.
- TreeModel.
- TreeTableModel.
- MergeSort. Clase para ordenar un conjunto de elementos
A continuación se muestra un ejemplo de utilización y extensión de estas herramientas para implementar un árbol de CheckBox.
El desarrollador a utilizado las clases proporcionadas, extendiéndolas para poder incluir CheckBox como elementos del árbol. Ha extendido la clase TreeTableModel para crear AttributesTreeTableModel y de ahí ya la clase necesaria para poder utilizar CheckBox, en este caso TreeTableModelWithCheckBoxes. Se ha implementado de tal forma para que en el panel aparezcan dos columnas en la primera el árbol y en la segundo un string en el que se indica el tipo del elemento adjunto en el árbol. Además mantiene el sitema de herencia, es decir, si se selecciona un nodo padre en el checkbox correspondiente, automáticamente se seleccionan todos los nodos hijos. El resultado ha sido el que se muestra en las imágenes.
El código necesario para realizar esta tarea es el siguiente.
Constructor de la clase de Test
AttributesTableTest att = new AttributesTableTest();
// Construimos un vector a partir de unos datos recogidos en un fichero XML
Vector att = describeFeatureType();
// Creamos el modelo con CheckBoxes al que se le pasa el vector anterior
TreeTableModelWithCheckBoxes model = new TreeTableModelWithCheckBoxes(att.get(0));
// Creamos un JTree con el modelo anterior
JTree tree = new JTree(model);
// Y por último creamos el Frame con el árbol
JFrame f = new JFrame();
f.addWindowListener(new java.awt.event.WindowAdapter() {
public void windowClosing(java.awt.event.WindowEvent evt) {
System.exit(0);
}
}
);
f.getContentPane().add(tree);
f.setBounds(0,0,600,500);
f.setVisible(true);
Esta clase extiende de BaseComponenr que a la vez extiende de JPanel. Esta herramienta permite crear un panel con un árbol y una lista. Se han implementado los listeners y los eventos necesarios (Mouse o Drag and Drop) para que sea posible añadir elementos del árbol a la lista. Esto se puede realizar arrastrando (Drag and Drop) o haciendo doble clic con el ratón en el árbol. Para eliminar un elemento de la lista se debe hacer doble clic sobre ese item. Sólo podemos tener dos niveles en el árbol,es decir, aparecería el nodo raíz, uno principal y otro secundario.
A continuación se observa una captura del elemento implementado.
El código necesario para utilzar la herramienta es el siguiente.
// Constructor
reeListContainer tlist = new TreeListContainer();
// Añadimos los elementos al árbol
tlist.addClass("uno", 0); //Añade un elemento a la raíz
tlist.addClass("dos", 1);
tlist.addClass("tres", 2);
tlist.addClass("cuatro", 3);
tlist.addEntry("uno-uno","uno",""); //Añade un elemento al nodo creado
tlist.addEntry("uno-dos","uno","");
tlist.addEntry("uno-tres","uno","");
tlist.addEntry("tres-uno","tres","");
tlist.addEntry("tres-dos","tres","");
tlist.addEntry("cuatro-uno","cuatro","");
tlist.addEntry("cuatro-dos","cuatro","");
tlist.addEntry("cuatro-tres","cuatro","");
tlist.addEntry("cuatro-cuatro","cuatro","");
tlist.addEntry("cuatro-cinco","cuatro","");
tlist.addEntry("cuatro-seis","cuatro","");
tlist.addClass("cinco",4);
// Expandimos el nodo "cero"
tlist.getTree().expandRow(0);
// Y por último añadimos la herramienta al "frame"
frame.getContentPane().add(tlist);
Esta clase extiende de JPanel y crea un panel con dos botones, Ok y Cancel. El tamaño de los botones y la disposición de los mismos en el panel por medio de un Layout, responden al estándar establecido por gvSIG para este tipo de paneles. El objetivo de esta clase es que todos los paneles "Aceptar/Cancelar" que creen los desarrolladores lo hagan de forma uniforme a este modelo. Además proporciona un constructor para añadir los Listeners oportunos a los botones, además de los métodos necesarios para añadir los listeners si se ha optado, en primera instancia, por el constructor por defecto.
A continuación se muestra una imágen del panel que crea esta clase.
El código utilizado para generar el ejemplo ha sido el siguiente.
El constructor de la clase y redimensionamos el panel
AcceptCancelPanel content= new AcceptCancelPanel(okActionListener,cancelActionListener);
content.setPreferredSize(new Dimension(300,150));
Los listeners que se ejecutan cuando se pulsa el botón OK o el botón Cancel
ActionListener okActionListener = new ActionListener(){
public void actionPerformed(ActionEvent arg0) {
System.out.println("Pulsado botón OK. ");
}
};
ActionListener cancelActionListener = new ActionListener(){
public void actionPerformed(ActionEvent arg0) {
System.out.println("Pulsado botón Cancel. ");
}
};
Clase que extiende de JPanel. Esta clase crea una barra de botones a la cual se le pueden añadir, eliminar y modificar la visibilidad de los botones que lo forman, se pueden alinear a la derecha o a la izquierda del panel o barra y crear un marco alrededor. La creación de los listeners para controlar las acciones asociadas a cada botón corren a cargo del desarrollador.
A continuación se muestra una captura de la barra de botones.
Mostramos ejemplos del código de utilización de esta clase:
// Creamos la barra de botones
ButtonBarContainer cont = new ButtonBarContainer();
// Establecemos como queremos alinearlos
cont.setButtonAlignment("right");
Añadimos los botones. Los parámetros indican el icono a mostrar, el "tip" informativo de la acción del botón y el orden que tiene el botón respecto de los demás botones en la barra
cont.addButton("edit-copy.png", "Copiar",0);
cont.addButton("edit-cut.png", "Cortar",1);
cont.addButton("edit-delete.png", "Borrar",2);
cont.addButton("editredo.png", "Deshacer", 3);
Otras opciones introducidas en el ejemplo
cont.disableAllControls(); //Desabilitamos todos los botones
cont.restoreControlsValue(); //Restauramos la visibilidad por defecto de los botones
cont.setComponentBorder(false); //No mostramos el "border"
cont.delButton(3); //Eliminamos el botón 3
Esta clase hereda de la clase abstracta DefaultBean que extiende JPanel con unos métodos para añadir, eliminar y cambiar Listeners. La clase Pager proporciona una serie de controles como son botones y una SlideBar para navegar con más facilidad por listas de objetos de gran extensión. Los botones son acceder al siguiente elemento, acceder al anterior, ir al primero y posicionarse en el último. Además incluye un campo de texto para indicar la posición exacta del elemento que deseamos localizar.
A continuación se muestra un ejemplo de los controles que proporciona la clase.
El código empleado para realizar el programa de test es el siguiente.
Constructor de la clase, en él se indica el elemento inicial y el final y como posicionar los elementos (horizontal o vertical)
Pager pag= new Pager(0,20,Pager.HORIZONTAL);
Se ha incluido un listener para capturar el valor del TextField y mostrarlo en la consola, el código necesario es el siguiente
pag.addListener(new BeanListener(){
public void beanValueChanged(Object value) {
System.out.println("("+((Integer) value).intValue()+")");
}
}
);
Reajustamos el tamaño
pag.setPreferredSize(new Dimension(300,200));
Y por último lo incluimos en el frame
Esta clase implementa un campo de texto y un slider. Esta clase crea un panel donde se incorporan un slider y un campo de texto en el que aparece el valor indicado por el slider. La herramienta incorpora internamente los listener necesarios para que todo cambio recogido en el listener se refleje en el campo de texto y al contrario, si se incluye un nuevo valor en el campo de texto el nuevo valor quedará indicado mediante la posición del slider. Se controla además que los valores introducidos en el campo de texto sean valores válidos.
A continuación se muestra una captura de su uso.
El código necesario para su utilización es el siguiente.
Constructor, cuyos parámetros son ancho, alto, valor mínimo, valor máximo y valor por defecto
SliderTextContainer slider = new SliderTextContainer(w - 12, h - 45, 0, 255, 255);
Y sólo queda añadir los listeners e incluirlo en el frame
frame.addComponentListener(this);
frame.getContentPane().add(slider);
El conjunto de clases de este componente está formado por:
- BoxesPanel
- DoubleSliderControlPanel
- GraphicChartPanel
- GraphicConteiner
- GraphicListener
Este control utiliza una librería externa "jfreechart-1.0.1" para implementar una herramienta para mostrar gráficos mediante líneas. Los gráficos mostrados se pasan en una matriz, la cual posee tantas filas como líneas queremos que nos muetre el "chart". Se dispone de métodos para modificar las líneas que se muestran por defecto. La librería implementa un menú PopUp que aparece pulsando el botón derecho en el podemos configurar las propiedades del gráfico, podemos guardarlo, hacer "zoom", establecer la escala e imprimirlo. Este menú PopUp es proporcionado por la librería.
A continuación se muestran unas imagenes de ejemplo.
A continuación se muestra el código necesario para crear este ejemplo.
El constructor de la clase GraphicContainer, ancho alto y "true" o "false", si queremos que muestre unos controles de tipo "Slider"
GraphicContainer graphic = new GraphicContainer(w - 12, h - 30, false);
A continuación se muestra una imágen con los controles de tipo slider, la acción a implementar por los controles queda en manos del desarrollador.
La herramienta muestra por defecto la siguiente gráfica.
Para modificarla e introducir los datos propios el código es el siguiente. Creamos dos matrices una de enteros y otra de Strings
int prueba [][] = new int [2][7];
prueba[0][0]=1;
prueba[0][1]=2;
prueba[0][2]=8;
prueba[0][3]=17;
prueba[0][4]=4;
prueba[0][5]=6;
prueba[0][6]=2;
prueba[1][0]=7;
prueba[1][1]=3;
prueba[1][2]=8;
prueba[1][3]=12;
prueba[1][4]=14;
prueba[1][5]=2;
prueba[1][6]=1;
String name[]=new String[2];
name[0]="Prueba1";
name[1]="Prueba2";
Y añadimos la llamada al método que se encarga de realizar el cambio de los valores por defecto a los introducidos por el usuario
graphic.getPGraphic().setNewChart(prueba,name);
Y por último lo añadimos al frame
frame.getContentPane().add(graphic);
Este control permite la creación de un panel con un botón y un campo de texto. Al pulsar el botón se creará un diálogo para seleccionar un fichero con la extensión "raw". La extensión es propia de la clase y no se puede cambiar en el fichero de prueba, hay que modificar la herramienta con el nuevo tipo de ficheros que queramos utilizar. Una vez seleccionado el fichero se mostrará el nombre del fichero seleccionado en el cuadro de texto.
A continuación se muestran unas capturas de su uso.
A continución se incluye el código necesario para utilizar la herramienta.
// Constructor de la clase
OpenFileContainer open = new OpenFileContainer();
// Reajustamos el tamaño
open.setComponentSize(w,h);
// Añadimos listeners
open.addComponentListener(this);
// Y por último añadimos el panel al "frame"
frame.getContentPane().add(open);
La clase Table extiende de BaseContainer, que a su vez extiende de JPanel. El objetivo de la herramienta es crear un panel que incluya una tabla. Existen varios modelos para generar la tabla, estos son:
- ListModel
- TreeRadioButtonModel
- RadioButtonModel
- CheckBoxModel
- TableButtonModel
El modelo que se generará por defecto si no se especifica uno será "ListModel". La herramienta incluye listeners para capturar y gestionar los eventos de control de la tabla para añadir una nueva entrada, eliminar una entrada específica y eliminar todas. Estos eventos son generados por botones incluidos en el panel, como se verá en las capturas realizadas.
A continuación se incluyen imágenes de los modelos desarrollados.
A continuación se incluyen fragmentos de código necesario para generar las imágenes mostradas anteriormente.
Constructor, común a todos lo modelos de tablas, se le pasa el ancho y el alto y el nombre de las columnas y el ancho que necesitamos para las columnas
TableContainer table = new TableContainer(w - 12, h - 45, columnNames, columnWidths);;
String[] columnNames = {"R", "G", "B", "T/F","columna 4"};
int[] columnWidths = {22, 22, 22,22, 334};
Luego se establece el modelo a utilizar en la tabla
table.setModel("TreeRadioButtonModel");
table.setModel("TableButtonModel");
table.setModel("RadioButtonModel");
table.setModel("LsitModel"); //o no poner nada, ya que por defecto se establece este modelo
Se debe incluir la llamada para inicializar la tabla
Generar las filas, por ejemplo en el modelo de RadioButton sería
Object[] row = {new Boolean(true), new Boolean(false), new Boolean(false), new Boolean(true),"texto 0"};
Object[] row1 = {new Boolean(false), new Boolean(true), new Boolean(false),new Boolean(false), "texto 1"};
Object[] row2 = {new Boolean(false), new Boolean(false), new Boolean(true), new Boolean(true),"texto 2"};
table.addRow(row);
table.addRow(row1);
table.addRow(row2);
Y por último añadir la tabla generada a la ventana y los listeners necesarios
frame.addComponentListener(this);
frame.getContentPane().add(table);
Esta clase se usa para proporcionar internacionalización a la librería gvSig-i18n.jar. Esto quiere decir que si se modificara la librería, los cambios no afectarían a las demás extensiones que utilizaran esta clase. La librería "Messages" (gvSIG-i18n.jar) realiza una traduccion mediante el método getText, este método recibe una clave (key) y proporciona la traducción correspondiente a esa clave.
Vamos a describir los pasos que se siguen actualmente para generar una distribución de
binarios de la aplicación gvSIG. Estos paquetes se generan como versiones para que los
testers puedan comprobar su estabilidad. Posteriormente, el cliente ya se encarga
de decidir si esa distribución (en adelante build) se publica de forma oficial en
la web, recibiendo un numero de versión final.
En el proceso de generación intervienen dos medios de comunicación:
- La página del wiki: Donde se encuentran disponibles para descargar los build y se informa de los cambios y novedades entre las versiones.
- Las lista de correo internas: Se utilizan para informar a los desarrolladores y a los usuarios del inicio y fin del proceso.
Los desarrolladores trabajan en dos lineas paralelas (ramas) sobre el sistema de gestión de fuentes (actualmente SVN):
- El TRUNK: donde se van desarrollando las funcionalidades y proyectos nuevos. Se considera no estable.
- Rama(Branch) de versión: es la bifurcación donde los fuentes se empiezan a preparar para una publicación de versión estable.
Según las necesidades, se genera la distribución sobre el Trunk o sobre otra rama.
A la hora de generar una nueva distribución se deberán realizar los siguientes pasos:
Notificar a la lista de usuarios (lista de desarrollo interna) que se va a proceder a la generación de una nueva distribución de la aplicación. Consecuentemente los desarrolladores deberán sincronizarse con el Repositorio SVN para subir los cambios que han ido realizando sobre el proyecto. Tras la notificación habrá que esperar cierto tiempo para que los desarrolladores hayan tenido tiempo para subir todos sus cambios.
Posteriormente se deberá sincronizar el Workspace para tener actualizado todos los cambios que acaban de ser subidos al repositorio (en el wiki están indicados los proyectos necesarios).
Es aconsejable que a la hora de sincronizarse con el repositorio y bajar los nuevos cambios, se eche una ojeada al wiki para comprobar que a nadie se le ha olvidado publicar algún cambio.
En el servidor SVN, hay que crear una carpeta con nombre tmp_build (borrarla antes si ya existiera) dentro de la carpeta "tags". En el comentario se deberá anotar "pre_v1_X_Build_X". En esta carpeta se copiará todo lo que se encuentra en la carpeta "branches/v10" (la ruta destino sería https://gvsig.org/svn/gvSIG/tags/tmp_build).
Una vez hecho esto, se deberá hacer un switch de nuestro proyecto(Team->Switch) de branches/v10 a tags/tmp_build. Según el plugin SVN que estemos usando en Eclipse, esto se deberá aplicar a proyecto(extensión) por proyceto, o a conjuntos de proyectos que se encuentran en el mismo directorio (extensions, libraries...).
Lo siguiente es limpiar el workspace para evitar interferencias entre el workspace y cualquier fichero que se hubiera generado en la versión anterior (en el build.xml del proyecto appgvSIG hay un target clean-all que limpia todos los .class y .jar y otro target clean_andami_gvSIG que limpia las extensiones que se encuentran dentro del proyecto Andami).
Una vez tenemos el workspace limpio ya podemos compilar el proyecto (en el build.xml del proyecto appgvSIG hay un target build-all), ejecutar la aplicación y realizar pruebas mínimas para comprobar que todo funciona correctamente.
Si todo funciona adecuadamente, de nuevo se vuelve a limpiar el workspace, y se vuelve a compilar, pero esta vez usando el target Generate-binaries-distribution. Este target realiza la misma función que el build-all pero en este caso se incrementa el buildnumber. De nuevo se vuelve a probar la aplicación comprobando en esta ocasión que el buildnumber se haya incrementado correctamente.
El siguiente paso es lanzar el build.xml o el distribucion_1.5.sh de la carpeta install del workspace, que es el encargado de la generacion de paquetes. La siguiente imagen muestra un resumen de los pasos que realiza dicho build o script:
- Preparamos la carpeta de instalación copiando las librerias y extensiones del proyecto.
- Usando el IzPack obtenemos el gvSIG.jar (siguiendo el fichero instalar.xml).
- Con el jar y el launcher preparamos el directorio temporal.
- Se comprime el directorio temporal (usando tar o 7z) para obtener el fichero tmp. Y finalmente,
- el fichero tmp se concatena con el script autoextraíble (sfx o sh) para definitivamente obtener el paquete de instalación bin o exe.
Para comprobar el funcionamiento de los binarios se lanza la instalación, y si todo funciona correctamente, se hace un commit de los buildnumbers de cada proyecto, con el comentario Vx_x_Build_xxx, y se renombra en el SVN la carpeta tags/tmp_build por tags/v1_X_Build_X.
De nuevo se vuelve a hacer un switch del proyecto, de manera análoga a la anterior, pero esta vez al branches/v10, y se hace un Merge (Team->Merge->Advanced) del proyecto. El source 1 será el Trunk y el source 2 será tags/v1_X_Build_X. De nuevo esto dependiendo del programa SVN que se use en Eclipse, se hará para cada proyecto o por conjuntos.
Finalmente se hace commit para los cambios oportunos en el trunk, se suben los paquetes generados al wiki/trac modificados con la nueva información de la nueva versión, y por último, se notifica a la lista sobre la nueva distribución, para que los testers ya puedan empezar a verificar el funcionamiento de la nueva distribución.
Aquí se muestra la estructura del paquete de instalación final (.bin o .exe) que se obtiene tras realizar el proceso de generacion.Este fichero sera el encargado de instalar la aplicación gvSIG. El fichero Jar obtenido con el IzPack contiene todas librerías y extensiones que forman parte del proyecto que nos haran falta para isntalar el gvSIG. El Launcher es un programa que lanza la instalación de gvSIG y se encarga de comprobar e instalar la jre (según la version del paquete puede estar incluida) y el autodescomprimible que es un script (.sfx o .sh) que hace que el paquete sea autoejecutable,para que podamos lanzar la instalación.
Este documento explica como instalar y configurar un servidor de bases de datos MySQL para ser usado con el geonetwork. aunque el documento se ha centrado en una máquina línux, el documento es válido también para windows, a excepción de los comandos que se utilizan en la instalación.
Lo primero que hay que hacer es instalar el servidor de MySQL. Para ello ejecutamos:
$apt-get install mysql-server-5.0
Como cliente vamos a usar un cliente de consola, aunque existen clientes gráficos que permiten hacer lo mismo. Para instalarlo ejecutamos lo siguiente:
$apt-get install mysql-client-5.0
Una vez que ya tenemos instalado el cliente y el servidor, vamos a crear la base de datos que utilizará Geonetwork. De aquí en adelante se asume que la base de datos se llamará geonetwok y que habrá un usuario geonetwork_user con una contraseña geonetwork_password que tendrá acceso a la misma. Para ello deberemos teclear desde una consola lo siguiente:
$mysql ?u root
Con esto nos estamos conectando a la base de datos como el usuario root sin contraseña. Si hemos creado una contraseña previamente tenemos que usar la opción -p. A continuación ejecutamos:
mysql> create database geonetwork character set utf8 collate utf8_unicode_ci;
mysql> grant select, insert, update, delete, drop, alter, create on table geonetwork.* to geonetwork_user@'localhost' identified by 'geonetwork_password';
mysql>exit;
Con esto ya tenemos la base de datos creada en UTF-8 y un usuario con los permisos necesarios para usarla.
Hay que editar el fichero TOMCAT_HOME/conf/server.xml y buscar la siguiente etiqueta:
<Connector
port="8080"
maxHttpHeaderSize="8192"
maxThreads="150"
minSpareThreads="25"
maxSpareThreads="75"
enableLookups="false"
redirectPort="8443"
acceptCount="100"
connectionTimeout="20000"
disableUploadTimeout="true"/>
Y añadirle el argumento URIEncoding="UTF-8", de manera que debe quedar algo como esto:
<Connector
port="8080"
maxHttpHeaderSize="8192"
maxThreads="150"
minSpareThreads="25"
maxSpareThreads="75"
enableLookups="false"
redirectPort="8443"
acceptCount="100"
connectionTimeout="20000"
disableUploadTimeout="true"
URIEncoding="UTF-8"/>
Lo primero que hay que hacer es bajarse el instalable desde la página del proyecto .. Geonetwork http://sourceforge.net/projects/geonetwork y bajarse la última versión estable 2.0.3 (a 2 de Agosto del 2007). A continuación, desde una consola hay que ejecutar:
java -jar geonetwork-intermap-2.0.3-install.jar
Y comenzará el proceso de instalación. Cuando se llega a la ventana donde hay que introducir los parámetros de conexión hay que poner, como nombre de usuario en nombre de usuario que se puso al configurar la base de datos (geonetwork_user) y como contraseña la que se especificó (geonetwrok_password). Hay que seleccionar MySQL como base de datos y en el campo donde hay que introducir la conexión hay que poner lo siguiente:
jdbc:mysql://localhost/geonetwork?useUnicode=true&characterEncoding=utf-8&mysqlEncoding=utf8
Después hay que finalizar el proceso de instalación. Una vez finalizado hay que enganchar en geonetwork con el tomcat periamente instalado. En la versión 5.5.23, habrá que ir al fichero $TOMCAT_HOME/conf/server.xml y añadir lo siguiente al final del fichero (justo antes de la etiqueta Host):
<Context
path="/geonetwork"
docBase="$GEONETWORK_HOME/web"
crossContext="false"
debug="0"
reloadable="false" />
A continuación habrá que editar el fichero $GEONETWORK_HOME/web/WEB-INF/config.xml y comprobar que la conexión a la base de datos está correctamente configurada. Para ello, hay que buscar la etiqueta resources (en torno a la línea 60) y ver las conexiones que hay. Hay que habilitar únicamente (con el atributo enabled la conexión a MySQL que debe tener un aspecto similar al siguiente:
<resource enabled="true">
<name>main-db</name>
<provider>jeeves.resources.dbms.DbmsPool</provider>
<config>
<user>geonetwork_user</user>
<password>geonetwork_password</password>
<driver>com.mysql.jdbc.Driver</driver>
<url>jdbc:mysql://localhost/geonetwork?useUnicode=true&amp;charac$
<poolSize>4</poolSize>
<reconnectTime>3600</reconnectTime>
</config>
</resource>
Para terminar hay que indicarle al Geonetwork que el servidor remoto está configurado en UTF-8. Para ello hay que modificar el fichero $GEONETWORK_HOME/web/xml/repositories.xml, buscar la etiqueta donde está el servidor remoto y añadirle la propiedad charset=utf-8 tal y como aparece aquí:
<Repository repository_dn="localhost:2100/geonetwork" name="Local GeoNetwork" type="Z3950" can_multiplex_sessions="no" >
<RepositoryProperty name="ServiceHost" value="localhost" />
<RepositoryProperty name="ServicePort" value="2100" />
<RepositoryProperty name="service_short_name" value="geonetwork" />
<RepositoryProperty name="default_record_syntax" value="xml" />
<RepositoryProperty name="default_element_set_name" value="s" />
<RepositoryProperty name="full_element_set_name" value="f" />
<RepositoryProperty name="charset" value="utf-8" />
<RepositoryProperty name="logo_src" value="http://www.k-int.com/collection_ico/sif.gif" />
</Repository>
Llegados a este punto ya podemos comprobar que el servidor funciona correctamente. Para ello debemos arrancar el Tomcat y escribir en un navegador http://localhost/geonetwork.
Para solucionar este problema hay que ir a la web del proyecto : Xalan http://xml.apache.org/xalan-j y bajarse los binarios de la librería (con la última estable 2.7 a día 2 de Agosto de 2007 funciona).
Hay que copiar los distintos jars que hay en el fichero comprimido (es posible que alguno no sea necesario) y copiarlos en el directorio GEONETWORK_HOME/web/WEB-INF/lib. A continuación hay que reiniciar el Tomcat y ya debería desaparecer el error.
Existe un bug en el Geonetwork 2.0.3 que hace que esto ocurra. Para solucionarlo har que irse a la clase org.fao.geonet.kernel.search.Z3950Searcher y sustituir el cuerpo del método isAlpha(string) por:
private boolean isAlpha(String text){
return false;
}
A continuación hay que compilar el proyecto. Existe una versión modificada del proyecto que se puede dercargar desde aquí
Este proceso se ha hecho utilizando la versión de Tomcat 5.5.23, la versión de Geonetwork 2.0.3, el Eclipse 3.3 y la máquina virtual de java 1.5.
Lo primero que hay que hacer es instalar un geonetwork con las fuentes. Para ello hay que seleccionar en el wizard de instalación la opción para que se instalen lo fuentes junto con los binarios ya compilados.
Una vez que ya está el Geonetwork instalado hay que abrir un Eclipse y hay que crear un nuevo proyecto "java project" y seleccionar la opción "from existing source", donde habrá que seleccionar la carpeta raíz donde hemos instlado el Geonetwork.
Si no lo hace automáticamente, habrá que añadir al classpath del proyecto las librerías que ha instalado el geonetwork que están en el directorio "web/WEB-INF/lib".
La maquina virtual con la que va a compilar el proyecto tiene que ser la misma con la que se va a ejecutar el tomcat*. A continuación debemos ejecutar el build del proyecto.
Una vez que hemos compilado el proyecto y no hemos obtenido ningún fallo deberemos iniciar el tomcat en modo debug. Para ello nos vamos al directorio donde está el Tomcat instalado y ejecutamos lo siguiente
$./catalina.sh jpda start
Esto arrancará el contenedor de aplicaciones en modo debug. A continuación deberemos abrir el Eclipse, y abrir el menú "debug". Debermos crear una "Remote Java Application" y seleccionar el proyecto donde se encuentre el Geonetwork instalado.
A continuación ya se pueden poner los puntos de interrupción que se necesiten. Al ejecutar el Geonetowrk desde el interfaz web se detendrá al llegar a cada uno de ellos.
Vamos a describir los pasos que se siguen actualmente para generar una distribución de fuentes de la aplicación gvSIG.
Al tener gvSIG una licencia GPL se deben distribuir los fuentes para quienes quieran descargarlos, pero éstos no se generan para cada distribución que se genere, debido a que demanda un trabajo bastante laborioso, por lo que se deberán generar cada vez que tengamos una distribución de binarios definitiva.
Debido a que la generación requiere que el fichero zip resultado contenga enlaces simbólicos
(para minimizar el tamaño del fichero), se necesita hacer este proceso en una maquina con FileSystem
con soporte para ello, osea Linux/Unix.
También requerimos (hasta nuevo aviso) un Eclipse v3.1 para mantener la máxima compatibilidad con las
versiones de este IDE.
Lo primero de todo es crear un nuevo workspace, y su nombre debe seguir el esquema ?gvSIG-VERSION-src?, por ejemplo "gvSIG-1_1-src". Se tendrán que incluir los proyectos de la lista que se pueden encontrar el la página de la versión (normalmente dentro de proyectos-que-la-componen/proyectos-de-la-version/).
También hay que tener en cuenta que el proyecto install no se distribuye.
Ahora, desconectamos todos los proyectos del SVN (seleccionamos todos los proyectos y TEAM->DISCONNECT)
y borramos el .metadata del workspace. Volvemos a abrir el Workspace pero usando el Eclipse 3.1 e importamos todos los
proyectos usando la opción File/Import.../Existing Projects into workspace/Next/Browse/Ok/Finish.
El proyecto ?binaries? contiene las librerías nativas para Windows, Linux y Mac OSX. Sin embargo, no hay que incluirlo tal cual, ya que las librerías no son usables tal cual están en el SVN. Hay que incluirlo con una distribución de archivos similar a la que tiene el directorio ?libs? de una instalación de gvSIG. Más abajo veremos como hacerlo.
Es necesario configurar Eclipse para asegurar que compilará en cualquier entorno:
Window ? Preferences ? General ? Editors ? Text file encoding ? Other (ISO-8859-1)
Window ? Preferences ? Java ? Compiler ?
* Compiler compliance settings: 5.0
* Desmarcar ?Use default compliance settings?
* Marcar ?Generated .class file compatibility?: 1.5
* Marcar ?Source compatibility?: 1.4
Se debe crear una configuración de ejecución (Run) de Eclipse, para que una vez ejecutados los builds, podamos ejecutarla correctamente. Esta configuración se puede copiar de una versión anterior de los fuentes (directorio .metadata/.plugins/org.eclipse.debug.core/.launches) o
hacer a mano. En la configuración se debe incluir los parámetros adecuados de Java (incluido el -Djava.library.path y la variable de entorno LD_LIBRARY_PATH. Las rutas asignadas a estas variables deben ser relativas, no absolutas, para que funcionen independientemente de dónde se deje caer el workspace.
Se creara una configuración de ejecución para cada Sistema Operativo,los parámetros son los siguientes:
- Pestaña Main: Project="_fwAndami", Main class="com.iver.andami.Launcher"
- Pestaña Arguments:
- Program arguments: "gvSIG gvSIG/extensiones"
- VM arguments:
- Para linux: "-Xmx500M -Djava.library.path=${workspace_loc}/binaries/linux"
- Para windows: "-Xmx500M -Djava.library.path=${workspace_loc}/binaries/w32"
- Para MacOS X: "-Xmx500M -Djava.library.path="${workspace_loc}"/binaries/mac"
- Pestaña Environment:
- Para TODOS: "PROJ_LIB" => "${workspace_loc}/_fwAndami/gvSIG/extensiones/org.gvsig.crs/data".
- Para linux: "LD_LIBRARY_PATH" => "${workspace_loc}/binaries/linux".
- Para windows: "PATH" => "${workspace_loc}/binaries/w32".
- Para MacOS X: "DYLD_LIBRARY_PATH" => "${workspace_loc}/binaries/mac".
Se debe compilar el workspace y ejecutar todos los builds en el orden correcto(se pueden usar los targets clean all y build all del build.xml de appgvSIG), y posteriormente ejecutar gvSIG, para comprobar que compila sin problemas y produce un gvSIG que funciona correctamente.
También hay que configurar un External Tool (menu Run-> External Tools -> External Tools) para que se pueda reconstruir el workspace. Para ello tendremos que seguir los siguientes pasos:
- Lanzar build.xml del proyecto appgvSIG usando el botón derecho sobre el fichero Run As/ Ant build. Esto nos generará un External Tools llamado appgvSIG build.xml.
- Desde el menú de External Tools, duplicar la definición appgvSIG build.xml y cambiarle el nombre a Build All.
- Modificar el target que lanzará por el de "eclipse-build-all".
- Desmarcar la opción Build befor lanuch de la pestaña Build.
- Marcar la opción Refresh resorces upon completion de la pestaña Refresh.
Es necesario dejar el External Tools llamado appgvSIG build.xml ya que sino, el eclipse siempre lanzaría el Build all por defecto al ejecutar el build.xml de appgvSIG.
Una vez comprobado que todo funciona correctamente, empezamos a limpiar el workspace.
Lo primero sera limpiar el directorio de binaries. Para prepararlo seguiremos los siguientes pasos:
- Dentro del directorio binaries ejecutar el target a usar se llama build-all.
- Revisar los enlaces del directorio biniaries/linux de crs. (Deberían de copiar los fichero a binaries/linux y revisar los enlaces simbólicos).
- Eliminar los subdirectorios de cada S.0
- Eliminar el directorio binaries/ant
También se deben borrar algunos ficheros y directorios que no se desea incluir.Estos se deben borrar desde dentro del eclipse, ya que al borrar algunos de esos directorios desde fuera de Eclipse se vuelve un poco loco. Por tanto, desde dentro de Eclipse vamos borrando los ficheros y directorios siguiendo los siguientes pasos:
- Renombrar el proyecto libUI_Components_praster a libUI_Components y modificar el nombre del proyecto en appgvSIG/build.xml.
- Desactivar la opción compilación automática en el menú Project ? Build Automatically.
- Lanzar el target clean-all del build.xml del proyecto appgvSIG.
- Recorrer todos los proyectos y borrar todas las subcarpetas: doc, test, src-test, bin-test, install (En los proyectos lib_UI, libUIComponent_praster y libDwg no borrar la carpeta doc sino solo su contenido y no tocar el src-test del libGDBMS).
- En el proyecto libGDBMS borrar también la carpeta conf y el fichero notas.txt.
- En el proyecto libInternationalization borrar también las carpetas: src-utils, test-data, utils-data y bin-utils.
- En el proyecto appCatalogAndGazetteer borrar también las carpetas: design, documents y servers.
- En el proyecto _fwAndami borrar también los ficheros: gvSIG.sh, gvSIG.bat y "por hacer.txt".
- Hacer un clean de los proyectos (menú Project ? Clean, desactivar el check de Start a build... con la
opción seleccionada Clean all proyects)
- En los proyectos libJCrs y extJDBC borrar también la carpeta dist.
- En el proyecto extOracleSpatial la carpeta dist y el contenido de la carpeta lib (incluido el _save, ya
que no tenemos permisos para distribuir los *.jar de Oracle).
- Borrar los directorio _fwAndami/gvSIG y _fwAndami/cachedir
- Del proyecto libJCRS quitar en las propiedades del proyecto Builders / crsgdal build.xml [Builder].
Ahora vamos a dejar el workspace configurado de la forma en que se la tiene que encontrar el usuario cuando
lo abra. Es importante que dejemos abierto el fichero appgvSIG/build.xml en el editor, ya que el
eclipse tiene un bug que nos podría dejar corrupto el External Tool que acabamos de crear. Dejaremos
solo la perspectiva Java abierta. Comprobamos que están los Run y External Tool definidos y que la
opción del menú Proyect ? Build Automatically esta desmarcada. Cerramos el Eclipse y no lo volvemos a abrir
en este workspace.
Ahora procedemos a borrar algunas librerías redundantes y ficheros que sobran. Esto se debe hacer desde fuera del Eclipse porque provoca que algunos proyectos no compilen inicialmente, pero esto se resolverá conforme el usuario final lance los proyectos en el orden especificado:
rm libFMap/lib/geojava.jar libFMap/lib/geotiff-jai.jar
rm libFMap/lib/jecw-*.jar libFMap/lib/jecwcompress-*.jar
rm libFMap/lib/jgdal-*.jar libFMap/lib/jmgeoraster.jar
rm libFMap/lib/jmrsid-*.jar libFMap/lib/jogr.jar
rm libFMap/lib/kxml2.jar libFMap/lib/tar.jar
rm libFMap/lib/remote-clients.jar libFMap/lib/cms.jar
rm libFMap/lib/gt2-legacy.jar libFMap/lib/gt2-main.jar
rm libFMap/lib/fmap.jar libFMap/lib/ojdbc14.jar
rm libFMap/lib/jGridShiftApi.jar libFMap/lib/geoapi-2.0.jar
rm libFMap/lib/log4j*.jar libFMap/lib/tempFileManager.jar
rm _fwAndami/lib/gvsig-i18n.jar _fwAndami/lib/iver-utiles.jar
Finalmente, se pueden borrar algunos ficheros en el directorio .metadata del workspace, que ocupan mucho espacio (esto sería conveniente hacerlo con el Eclipse cerrado). Desde la consola:
rm .metadata/.plugins/org.eclipse.jdt.core/*.index
rm .metadata/.plugins/org.eclipse.jdt.core/savedIndexNames.txt
touch .metadata/.plugins/org.eclipse.jdt.core/savedIndexNames.txt
rm -R .metadata/.plugins/org.eclipse.core.resources/.history/*
rm .metadata/.plugins/org.eclipse.debug.ui/launchConfigurationHistory.xml
find . -name .classpath -exec touch {} ';'
Lo último que quedaría por hacer es el empaquetado. Se debe añadir algunos ficheros build.xml y LEEME en la raíz del Workspace. Desempaquetar unas fuentes anteriores y copiar los ficheros faltantes. Hay que revisar los distintos LEEME y revisar si las instrucciones siguen siendo correctas. Actualizar los números de versión y los proyectos a compilar.
Los fuentes se empaquetan en un fichero ZIP. Normalmente siguen la nomenclatura gvSIG-VERSION-src.zip, por ejemplo ?gvSIG-1_1-src.zip?. El fichero zip se debe generar de forma que se respeten los enlaces simbólicos (en caso contrario algunas librerías se copiarían por duplicado y ocuparían mucho espacio). En Linux, para ello usamos el comando:
zip -y9r gvSIG-1_1-src.zip gvSIG-1_1-src
Actualmente se genera un único fichero tanto para Linux como para Windows. Se está valorando crear dos ficheros diferentes, para hacer más ligera su descarga (ya que no sería necesario incluir las librerías de un sistema en el otro).