Introducción a gvSIG

Introducción a gvSIG

Introducción

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:

introduccion-a-gvsig-img/gvsig0.png

Diagrama base de subsistemas de la aplicación gvSIG

Subsistema gvSIG

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.

introduccion-a-gvsig-img/gvsig1.png

Diagrama de bloques del subsistema de gvSIG

Subsistema Fmap

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.

introduccion-a-gvsig-img/fmap1.png

Diagrama de bloques de FMap

DataSources y Drivers

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.

introduccion-a-gvsig-img/subdriver1.png

Diagrama de bloques de Subdriver

RemoteServices: Contiene las herramientas necesarias para unificar el acceso a datos remotos.


gvSIG


Andami

Introducción
Descripción general

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.

Relación con el resto 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.

Diagrama de bloques de la relación de Andami con el resto de gvSIG.

Diagrama de bloques de la relación de Andami con el resto de gvSIG.

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.

Servicios que proporciona

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:

Vista general

Vamos a mostrar una perspectiva general de los bloques funcionales de Andami, explicando la funcionalidad aportada por cada uno.

Diagrama de bloques funcionales de Andami.

Diagrama de bloques funcionales de Andami

Gestión de ventanas

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 .

Gestión de la interfaz gráfica de usuario

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.

Gestión de 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.

Servicios a los plugins

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:

Se puede encontrar más detalles en el capítulo Servicios a los plugins.


Plugins y extensiones
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).

El fichero config.xml

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.

Jerarquía de etiquetas válidas de plugin-config.xml

Jerarquía de etiquetas válidas de plugin-config.xml

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.

<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:

<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:

<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:

<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:

<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:

<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:

<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.

Detalle de dos herramientas seleccionables (la izquierda está seleccionada)

Detalle de dos herramientas seleccionables (la izquierda está seleccionada)

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:

<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:

<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:

Detalle de un combo-scale

Detalle de un combo-scale


Extensiones
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:

Diagrama de las clases implicadas en la creación de extensiones

Diagrama de las clases implicadas en la creación de extensiones

Todas las extensiones implementan la interfaz IExtension, que incluye los siguientes métodos:

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).

Detalle de un menú activo y otro desactivado.

Detalle de un menú activo y otro desactivado.

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):

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:

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:


Las ventanas de Andami
Introducció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.

Diagrama de las principales clases implicadas en la gestión de ventanas

Diagrama de las principales clases implicadas en la gestión de ventanas

La ventana principal
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.

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.

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().

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().

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().

Detalle de mensajes de información, aviso y error en la barra de estado

Detalle de mensajes de información, aviso y error en la barra de estado

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.

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.

Detalle de la barra de progreso

Detalle de la barra de progreso

Skins de Andami (libCorePlugin)

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.

Clases principales de CorePlugin y clases relacionadas de Andami.

Clases principales de CorePlugin y clases relacionadas de Andami


Gestión de ventanas
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.

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 |:

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.

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.

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.

Establece la anchura (en píxeles) de la ventana.

Devuelve la altura (en píxeles) de la ventana.

Establece la altura (en píxeles) de la ventana.

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.

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í.

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.

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í.

Obtiene la posición y dimensiones (en píxeles) de la ventana.

Establece la posición y dimensiones (en píxeles) de la ventana.

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.

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í.

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.

Establece el título de la ventana.

Obtiene el identificador de ventana.

Establece el identificador de ventana.

Devuelve cierto (true) si la ventana está cerrada, falso (false) en caso contrario.

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.

Devuelve cierto (true) si la ventana es minimizable, falso (false) en caso contrario.

Devuelve cierto (true) si la ventana es maximizable, falso (false) en caso contrario.

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.

Devuelve cierto (true) si la ventana está maximizada, falso (false) en caso contrario.

Maximiza o restaura la ventana dependiendo del parámetro maximized.

Devuelve cierto (true) si la ventana es modal, falso (false) en caso contrario.

Devuelve cierto (true) si la ventana es modeles, falso (false) en caso contrario.

Devuelve cierto (true) si la ventana está actualmente en modo paleta (flotante), falso (false) si está empotrada en la ventana correspondiente.

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.

Devuelve cierto (true) si la ventana es redimensionable, falso (false) en caso contrario.

Devuelve cierto (true) si la ventana es visible, falso (false) en caso contrario.

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.

Devuelve cierto (true) si el estado de esta ventana debe guardarse en los proyectos de gvSIG, falso (false) si debe ser ignorada.

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).

Diagrama que muestra las principales clases relacionadas con el gestor de ventanas

Diagrama que muestra las principales clases relacionadas con el gestor de ventanas

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

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.

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

Cierra todas las ventanas abiertas.

Cierra la ventana window.

Cierra la ventana Singleton que tenga como modelo el objeto model.

Cierra la ventana Singleton cuya clase y modelo se pasan como parámetros.

Métodos para obtener las ventans existentes

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.

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

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.

Centra la ventana panel.

Actualiza las propiedades de la ventana window usando los valores especificados en el objeto wi.

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

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().

Restaura el icono de ratón que hubiese antes de comenzar la operación, y reactiva el manejo de eventos de la ventana principal.

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):

public void toPalette()

Pide a la ventana que se transforme en modo flotante.

public void restore()

Pide a la ventana que se transforme en modo empotrado.

isPalette()

Devuelve cierto si la ventana está en modo paleta (flotante), falso si está en modo empotrado.


Servicios a los plugins
La clase PluginServices

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.

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.

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"
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.

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:

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);
}
Persistencia de datos (plugin-persistence.xml)

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
  }
}
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 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:

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:

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());
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. 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.

Accesso a recursos del plugin

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);
Acceso al portapapeles

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().


El proyecto y sus documentos


Vista
MapOverview
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

../fmap/images/mapcontrol/toollistenericons/ictlcruxcursor.png

Cursor de MapOverviewListener

Diagrama

La figura 1 muestra el diagrama de clases que resume la implementación de MapOverview según se ha explicado:

images/projectanddocuments/vista/mapoverview/dcmapoverview.png

Figura 1: diagrama de clases simplificado de MapOverview.

Captura
images/projectanddocuments/vista/mapoverview/ejmapoverview.png

Figura 2: se muestra una vista de gvSIG, en donde el usuario ha seleccionado en el localizador un área rectangular que abarca toda la Comunidad Valenciana. Una vez seleccionada, se actualiza la vista principal, (MapControl), con el extent seleccionado en el localizador, ajustado al área disponible para visualizarlo.


FMap


Introducción

Introducción
Introducción

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.

Diagrama
images/introduction/dcpaqueteslibfmap.png

Diagrama 1: Visión estructural de la librería libFMap. Se muestra solo la división al primer nivel para simplificar el diagrama.

Enlaces de Interés

MapControl

Behavior
Introducción

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.

Diagramas

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.

images/mapcontrol/dcbehavior.png

Diagrama 1: Diagrama de clases centrado en Behavior.

Tipos de behavior en libFMap

El diagrama 2 muestra los tipos de comportamientos básicos implementados en la librería libFMap.

images/mapcontrol/dcbehaviortypes.png

Diagrama 2: Tipos de comportamiento implementados en 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
Enlaces de interés

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.

Eventos relacionados con las ToolListeners
Introduccción

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.

Tipos

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:

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:

PointEvent: evento asociado a tool listeners que requieren que el usuario seleccione un punto.

Información adicional que aporta:

RectangleEvent: evento asociado a tool listeners que requieren que el usuario seleccione un área rectangular.

Información adicional que aporta:

Diagrama
images/mapcontrol/dceventsrelatedtoollisteners.png

Diagrama: Muestra la estructura y las relaciones de este tipo de eventos con las ToolListeners básicas, el resto hereran de ellas, y por tanto mantienen la misma relación con estos eventos.

MapControl
Introducción

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.

Descripció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.

Pintado de MapControl

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:

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.
Gestión de Excepciones

MapControl permite registrar listeners, de tipo ExceptionListener, con los que recibir notificación de expcepciones producidas:

Diagrama

El diagrama 1 muestra los principales elementos que intervienen con MapControl.

images/mapcontrol/smalldcmapcontrol.png

Diagrama 1: diagrama de clases simplificado de MapControl. Versión en tamaño reducido.

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.
Funcionalidad
Enlaces de interés
ToolListener
Introducció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. 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).

Diagramas

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 las interfaces para todas las herramientas
images/mapcontrol/dcinterfacesbasetoolslibfmap.png

Diagrama 1: diagrama de clases con las interfaces base que implementan todos los listeners de las herramientas definidas en libFMap. El color morado representa un elemento del diagrama global de MapContext.

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.

images/mapcontrol/dclistenersimplcirclelistener.png

Diagrama 2: diagrama de clases: Listeners de herramientas de libFMap que implementan CircleListener.

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
images/mapcontrol/toollistenericons/ictlrulercursor.png
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).

images/mapcontrol/dclistenerstypepanlistener.png

Diagrama 3: diagrama de clases: Listeners de herramientas de libFMap que implementan PanListener.

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
images/mapcontrol/toollistenericons/ictlzoomincursor.gif
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
images/mapcontrol/toollistenericons/ictlcruxcursor.png
PanListenerImpl libFMap Actualiza el ViewPort del objeto MapControl asociado con un nuevo extent. Sí. MoveEvent
images/mapcontrol/toollistenericons/ictlhand.png
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
images/mapcontrol/toollistenericons/ictlhand.png
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.

images/mapcontrol/smalldclistenerstypepointlistener.png

Diagrama 4: diagrama de clases: Listeners de herramientas de libFMap que implementan PointListener.

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
images/mapcontrol/toollistenericons/ictlinfocursor.gif
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
images/mapcontrol/toollistenericons/ictllink.png
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
images/mapcontrol/toollistenericons/ictlcruxcursor.png
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
images/mapcontrol/toollistenericons/ictlpointselectcursor.png
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
images/mapcontrol/toollistenericons/ictlpointselectcursor.png
SelectImageListenerImpl libFMap ToolListener anticuado que permitía tener SelectImageListener en appgvSIG para la selección de capa raster. Sí. PointEvent
images/mapcontrol/toollistenericons/ictlpointselectcursor.png
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
images/mapcontrol/toollistenericons/ictlpointselectcursor.png
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
images/mapcontrol/toollistenericons/ictlpointselectcursor.png
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
images/mapcontrol/toollistenericons/ictlzoompixelcursor.gif
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
images/mapcontrol/toollistenericons/ictlzoomoutcursor.gif
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
images/mapcontrol/toollistenericons/ictlzoomoutcursor.gif
ZoomOutRightButtonListener libFMap Funciona igual que ZoomOutListenerImpl, pero sólo como respuesta al pulsado del botón derecho del ratón. Sí. PointEvent
images/mapcontrol/toollistenericons/ictlzoomoutcursor.gif
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
images/mapcontrol/toollistenericons/ictlzoompixelcursor.gif
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.

images/mapcontrol/dclistenerstypepolylinelistener.png

Diagrama 5: diagrama de clases: Listeners de herramientas de libFMap que implementan PolylineListener.

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
images/mapcontrol/toollistenericons/ictlareacursor.png
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
images/mapcontrol/toollistenericons/ictlareacursor.png
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
images/mapcontrol/toollistenericons/ictlrulercursor.png
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
images/mapcontrol/toollistenericons/ictlrulercursor.png
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
images/mapcontrol/toollistenericons/ictlpolygoncursor.png
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
images/mapcontrol/toollistenericons/ictlpolygoncursor.png
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.

images/mapcontrol/dclistenerstyperectanglelistener.png

Diagrama 6: diagrama de clases: Listeners de herramientas de libFMap que implementan RectangleListener.

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
images/mapcontrol/toollistenericons/ictlzoomincursor.gif
RectangleSelectionListener libFMap De las capas vectoriales que estén activas, selecciona todas las features que intersecten con el área rectangular definida. No. RectangleEvent
images/mapcontrol/toollistenericons/ictlrectselectcursor.gif
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
images/mapcontrol/toollistenericons/ictlrectselectcursor.gif
SaveRasterListenerImpl libFMap, extRasterTools Almacena el área rectangular definida, en coordenadas del mapa y del mundo. Sí. RectangleEvent
images/mapcontrol/toollistenericons/ictlrectselectcursor.gif
SaveRasterListener extRasterTools Permite guardar el área rectangular seleccionada, como un fichero raster, vía un panel cone opciones de salvado. Sí. RectangleEvent
images/mapcontrol/toollistenericons/ictlrectselectcursor.gif
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
images/mapcontrol/toollistenericons/ictlrectselectcursor.gif
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
images/mapcontrol/toollistenericons/ictlzoomincursor.gif
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
images/mapcontrol/toollistenericons/ictlzoomincursor.gif

MapContext

MapContext
Introducción

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 .

Descripción

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.

../images/mapcontext/dcmapcontext.png

Diagrama 1: diagrama de clases de MapContext. El paquete "Groups of Registered Listeners" representa un conjunto de listeners que MapContext puede registrar, y lanzar cuando recibe algún evento de los soportados por alguna de ellas.

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.

images/mapcontext/smalldceventbuffer.png

Diagrama 2: diagrama donde se muestra la clase EventBuffer, que usa MapContext para trabajar con conjuntos de eventos como atómicos, y donde se muestran los interfaces que implementa.

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).

images/mapcontext/dcgroupsreglists.png

Diagrama 3: conjunto de listeners que puede registrar MapContext y lanzar cuando recibe algún evento de los soportados por alguna de ellas.

Atributos de MapContext
Conversión de Unidades de Medida

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.

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
Funcionalidad
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
Introducción

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).

ViewPort en el contexto de MapContext

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.

Visión Global
images/mapcontext/dcviewport.png

Diagrama 1: diagrama de clases simplificado de ViewPort en MapContext. Se muestran las clases e interfaces de sus atributos.

Atributos del ViewPort
Unidades de Medida

Las unidades de medida soportadas, tanto para distancias como para del mapa original son:

Funcionalidad
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.

Ejemplo
images/dirimejviewport/ejviewportim1.png

Imagen 1: Una vista de gvSIG, donde se indica la dimensión del área disponible para visualizar las capas.

images/dirimejviewport/ejviewportim2.png

Imagen 2: La vista de la Imagen 1, sobre la que se ha cargado dos capas. El/la usuario/a selecciona un área de interés, esta será el extent. Así, cuando deje de seleccionar, se recalculará el nuevo adjustedExtent.

images/dirimejviewport/ejviewportim3.png

Imagen 3: La vista de la Imagen 2, donde se visualiza el área de interés seleccionada, adaptada a las dimensiones disponibles. Se puede observar como se ha ampliado el ancho seleccionado, por mantener una proporción al aspecto del área disponible de visualización.


Layers

Estatus de las Capas
Introducción

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.

images/layers/dcflayerstatus.png

Diagrama de clases que muestra la relación de FLayerStatus con las capas de libFMap.

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.
Status por defecto al crear una capa
Estado Activo [1]
Activa No
Visible
Disponible
Sucia No
En TOC
Editándose No
Puede escribirse No
Caché de capas dibujadas No
[1]"Activo" equivale a "true" a nivel de programación.
Capturas

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:

images/imejsestscapa/ej1estadocapa.png

El usuario tiene esta vista en gvSIG con las capas WFS "embalses" e "hidrografía" en el TOC, visibles ("checkboxs" seleccionados), disponibles (dado que sino no se habrían visualizado sus datos), y estando la capa "embalses" activa.

images/imejsestscapa/ej2estadocapa.png

Igual a la imagen anterior, pero ahora ambas capas están activas (seleccionadas).

images/imejsestscapa/ej3estadocapa.png

Ahora, el usuario ha deseleccionado los "checkbox's" de ambas capas, y, por tanto, no están visibles. Por otro lado, la capa "hidrografía" está activa, y ambas se mantienen disponibles dado que no se nos informa de lo contrario en el icono asociado.

images/imejsestscapa/ej4estadocapa.png

Visible sólo la capa "embalses", y activa sólo "hidrografía".

images/imejsestscapa/ej5estadocapa.png

El usuario ha creado una vista en gvSIG en la que sólo ha cargado una capa (1 capa en TOC), que está activa, pero no disponible (lo indica el aspa roja del icono de la izquierda asociado a la capa). Dado que la capa no está disponible, deja de estar visible.

images/imejsestscapa/ej6estadocapa.png

Cuadro de diálogo que aparece cuando se intenta editar una capa que no tiene permiso de escritura.

images/imejsestscapa/ej7estadocapa.png

El usuario ha intentado cargar una capa en una vista de gvSIG, pero se han producido errores y por tanto deja de estar disponible.

images/imejsestscapa/ej8estadocapa.png

Capa cargada en TOC, disponible, visible, activa, con permiso de escritura, y en edición.

Layers - FLayer, FlyrDefault, FLayers
Introducción

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:

images/layers/dclayers.png

Diagrama 1: relación entre las clases e interfaces del grupo Layers.

Descripción y funcionalidad
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 [1] o unmarshall.
[1]Marshall es como se denomina al proceso por el que un objecto se serializa usando XML, es decir, devuelve su información de modo que se pueda transmitir por una conexión, o persistir en un fichero o memoria, siguiendo el estándar XML para estructurar dicha información. Al proceso inverso se le denomina Unmarshall.
Enlaces relacionados
Pintado y escritura de capas
Pintado

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.

Escritura de una capa

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;
}
Enlaces relacionados
Tipos de capas

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.

images/layers/smalldctypesoflayers.png

Diagrama 2: Diagrama simplificado que muestra los principales tipos de capas declaradas en la librería libFMap u otro proyecto de gvSIG.

Nombre Ubicación [1] 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''[2] 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.

[1]La columna "Ubicación" indica en qué proyecto está declarada la capa.
[2]Marshall es como se denomina al proceso por el que un objecto se serializa usando XML, es decir, devuelve su información de modo que se pueda transmitir por una conexión, o persistir en un fichero o memoria, siguiendo el estándar XML para estructurar dicha información. Al proceso inverso se le denomina Unmarshall.

Plugins


Publish Extension

Motivación y objetivos

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

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.

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

Terminología y convenciones

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.

Requerimientos iniciales
#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:

  1. Leyenda simple
  2. Leyenda de valores únicos
  3. 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:

  1. Directorio donde se generan los ficheros de configuración.
  2. Directorio temporal.
  3. 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:

  • tolerancia
  • mode debug

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).


Funcionaliadad

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.

Diagrama de casos de uso de alto nivel

Diagrama de casos de uso de alto nivel

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

Diagrama de casos de uso de asociados a Mapserver

Diagrama de casos de uso de asociados a Mapserver

Casos de uso asociados a Geoserver

Diagrama de casos de uso de asociados a Geoserver

Diagrama de casos de uso de asociados a Geoserver

Contexto

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.

Subsistemas de análisis del framework de publicación

Subsistemas de análisis del framework de publicación.

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.

Pruebas

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:

  1. Publicación de distintas fuentes de datos tales como shapefiles, postGIS y ráster ECW y Geotiff.
  2. Publicación de la leyenda, valores únicos e intervalos.
  3. Publicación de la simbología para puntos, líneas y polígonos
  4. 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


Subsistemas

modelServers
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.

Fallo al cargar la imagen. Clases de análisis del subsistema modelServers

Clases de análisis del subsistema modelServers

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:

Fallo al cargar la imagen. Diagrama de casos de uso del subsistema libServers

Diagrama de casos de uso del subsistema libServers

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
Fallo al cargar la imagen

Clases de diseño del subsistema modelServers

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


InfoProject
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.

Clases de análisis infoProject

Clases de análisis infoProject

Funcionalidad

La funcionalidad que se espera de este subsistema es la de consultar información sobre los siguientes elementos:

Esta consulta de información se puede dividir en los siguientes casos de uso:

TEXTO

Casos de uso de infoServices

Diseño
Fallo al cargar la imagen. Clases de diseño del subsistema infoProject

Clases de diseño del subsistema infoProject

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


GUI
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.

Fallo al cargar la imagen. Clases de análisis del subsistema gui

Clases de análisis del subsistema gui

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.

Fallo al cargar la imagen. Clases de análisis del controlador por defecto

Clases de análisis del controlador por defecto

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().

Fallo al cargar la imagen

Clases de diseño del subsistema gui

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


Plugins
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.

Subsistemas de análisis de los plugins

Subsistemas de análisis de los plugins


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 de análisis de publishMapserver

Subsistemas de análisis de publishMapserver


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.

GUI referente a WMS de publishMapserver

GUI referente a WMS de publishMapserver


libMapserver Contexto
libMapserver

libMapserver


Catálogo y Nomenclator

Adding a new Gazetteer protocol

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:

TEXTO
Methods to implement

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.

Driver register

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());
Creating a thesaurus tree (searches by cathegory)

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.


Extensión de metadatos

Introducción

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.

Motivación y objetivos

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:

Terminología y convenciones
Requerimientos funcionales

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.

Recursos del sistema. Niveles de documentación.

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.

Extracción de metadatos

La extensión del Gestor de Metadatos incorpora en gvSIG la capacidad de extracción semiautomática de metadatos de las fuentes de datos. Hablamos de extracción semiautomática porque los metadatos serán obtenidos de diversas maneras:

- Extracción automática de metadatos: 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 de metadatos; que no puedan ser extraídos de forma automática o por requisito de usuario podrán ser editados manualmente 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.

Formatos de datos

La extensión del Gestor de Metadatos incorpora en gvSIG la capacidad de extracción y semiautomática y edición de metadatos de diversas fuentes de datos y recursos del sistema.

En una primera fase de diseño de la extensión, se consideran como objetos a documentar las capas con los siguientes formatos bien conocidos (Well-Known Formats, WKF):

En una segunda fase de diseño, en cambio, se ampliaría tratando diferentes recursos del sistema como vistas, features, etc y capas de fuentes de formatos que actualmente puede leer GDAL, tratando de obtener la mayor información posible para cada formato. Fases posteriores contemplarán también la integración con el almacenamiento de metadatos de capas raster, las cuales necesitan almacenar metadatos especiales para el rendimiento óptimo del sistema.

Capas vectoriales

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.

Metadatos inherentes al sistema y al usuario

Entre los metadatos extraídos semi-automáticamente se encuentran datos del sistema, como puede ser la hora, usuario, etc. Que serán utilizados para crear los metadatos del recurso sobre el que se está trabajando.

Flujo de extracción de metadatos

La extracción automática de metadatos se realizará cada vez que el usuario realice las siguientes operaciones sobre una capa:

En el caso de estar trabajando con capas que procedan de ficheros con formato conocido, se extraerán automáticamente los metadatos y se almacenaran con el mismo nombre y ubicación que el fichero y con una extensión con nombre mdml.

Los metadatos se extraerán semi automáticamente utilizando los drivers de gvSIG de lectura de ficheros con formato conocido, de igual forma se estudiará la posibilidad de integrar parte de la funcionalidad del wrapper desarrollado por la UPM sobre GDAL para la extracción automática de metadatos.

Gestión de metadatos de contactos del usuario

El gestor de metadatos incorpora un modulo de gestión de contactos. Desde el menú de gvSIG se podrá acceder a un formulario donde el usuario podrá añadir contactos que serán usados posteriormente para formar parte de los metadatos, estableciendo uno de los contactos por defecto y que podrá ser modificado en cualquier momento durante la ejecución de gvSIG. Otro formulario puede servir para guardar datos personales del usuario, para su uso también en el cumplimiento de registros de metadatos. Estos datos minimizarán el trabajo manual a la hora de introducir datos del creador y contactos en los múltiples registros de metadatos a lo largo del tiempo.

El gestor de datos de contactos tendrá la funcionalidad de exportar/importar contactos con la finalidad de poder compartir datos de contacto entre diferentes usuarios. El formato de almacenamiento persistente de los metadatos de contacto será mdml, conteniendo el esquema de este formato un bloque que contempla el almacenamiento de los contactos.

Generación de un formato de metadato interno

Para la persistencia de los metadatos en el sistema se desarrollará un esquema de XML para la creación de un formato de fichero XML. La extensión de este fichero XML de metadatos será .MDML (Metadata Mark-up language), estos ficheros mdml es un superconjunto de los formatos de metadatos más utilizados (ISO, Dublín-Core, NEM), el módulo MDExchanger permite exportarlo a diferentes formatos estándar:ISO 119115 en base al 119139. Este fichero contendrá:

  1. Información proporcionada por gvSIG: (nombre de fichero, extensión, fecha de creación, escala)
  2. Información proporcionada por GDAL/OGR (sistema de referencia, bounding box / extent / datum / ellipsoide, etc.)
  3. Información proporcionada por el usuario (título, resumen, lineaje, etc..)

Este fichero, tal y como hemos visto en secciones anteriores, será almacenado junto a los datos. gvSIG manejará datos y metadatos como parte de una misma cosa, pasando así de una separación a una integración de los datos con sus metadatos.

Actualmente en gvSIG existe el formato RMF (raster meta file) que contiene metadatos de las capas raster, este fichero está orientado al almacenamiento de información para garantizar un rendimiento óptimo cuando se trabaja con datos raster. También se pretende realizar un estudio para la integración de estos metadatos con el formato mdml, por ejemplo añadiendo en el MDExchanger la capacidad de transformación de un formato a otro mediante, por ejemplo, el uso de hojas de estilo.

Visualización y edición de metadatos.

La extensión del Gestor de Metadatos incluye un módulo de visualización y edición manual de metadatos, el usuario podrá visualizar en cualquier momento los metadatos asociados a una capa y hacer las modificaciones que crea convenientes. Este editor informará de forma gráfica el estado de cada metadato así como su carácter dentro de la ISO y NEM, es decir podremos saber de forma rápida y visual que metadatos de carácter obligatorio por ejemplo faltan por rellenar en nuestro conjunto de datos. El visor-editor de metadatos permite la visualización de metadatos de acuerdo a los diferentes perfiles de metadatos soportados por gvSIG, de forma que un usuario puede alternar las vistas y ver los metadatos según el perfil mdml, ISO, etc. Esta funcionalidad permite al usuario saber a simple vista si dispone, por ejemplo, de todos los metadatos obligatorios según el perfil al que quiera exportar o publicar sus metadatos.

Desde el visor el usuario tendrá la posibilidad de invocar al módulo publicador de metadatos.

Otros editores de Metadatos.CatMDEdit

Si el usuario desea visualizar o modificar sus datos desde otros editores de datos, como por ejemplo CatMDEdit, el editor de metadatos incluido dentro de la extensión permitirá exportar los metadatos a formato ISO-NEM para que puedan ser utilizados desde otras aplicaciones que lean este formato estándar.

Importar y Exportar metadatos.

En el caso de que los usuarios quieran compartir metadatos asociados a los datos sobre los que trabajan, gvSIG podrá tanto exportar sus metadatos a formatos conocidos de metadatos como importar metadatos externos en formatos conocidos y asociarlos a sus conjuntos de datos. Desde el visor-editor de metadatos el usuario podrá transformar los metadatos a los diferentes formatos de metadatos soportados en cada momento. Esta funcionalidad vendrá implementada en el módulo MDExchanger, que tras validar los metadatos realizará la transformación a los diferentes formatos mediante hojas de transformación.

Configurar y personalizar el Gestor de Metadatos

Existen varios niveles de compromisos configurables por el usuario a la hora de realizar la extracción automática de metadatos. El usuario podrá configurar en gvSIG el nivel de severidad con la que gvSIG tratará la ausencia de metadatos, de esta forma la extracción automática de algunos metadatos puede ser cancelada por petición del usuario.

Contexto

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.

No se encuentra la imagen "Diagrama de contexto inicial"

Diagrama de contexto inicial: interacción entre extMetadata y FMap

Tres son los módulos que conforman la extensión: mdManager, mdPublish y mdExchange. A continuación, se muestran las interacciones existentes entre ellos.

No se encuentra la imagen "Diagrama de contexto de extMetadata"

Diagrama de contexto de extMetadata: interacción entre los módulos de la extensión

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.

Pruebas

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:

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.

Funcionalidad

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:

Diagrama de casos de uso: extracción automática de metadatos

Diagrama de casos de uso para la extracción automática de metadatos

Realmente, nos referimos a extracción semiautomática porqué los metadatos serán obtenidos de diversas maneras:

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:

Diagrama de casos de uso general de metadatos

Diagrama de casos de uso general de 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 gestor de metadatos como actor

Diagrama de casos de uso con el módulo gestor de metadatos como actor

mdExchange Diagrama de casos de uso del módulo de publicación

   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.

Diagrama de casos de uso de mdPublish

Diagrama de casos de uso de mdPublish


Arquitectura
Introducción
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:

Requerimientos previos

Tenemos unos requerimientos previos empleados como base para el desarrollo del prototipo de Metadatos, A modo de resúmen, serían:

Requerimientos de arquitectura
Soporte de Metadatos en gvSIG
Persistencia de Metadatos
Registro de Metadatos
Referencias externas
Dublin Core
ISO 19115
Wikipedia

Definiciones de algunos términos:

Implementaciones y APIs
Geonetwork

Análisis
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.

diagramas/relacion-modelo-datos-core-img

Relación con el modelo de datos de gvSIG

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.

Requerimiento RQ1.1: Persistencia de Metadatos

Dado que los Metadatos formarán parte intrínseca de un proyecto gvSIG, deberán almacenarse como parte del mismo, junto con los propios datos del proyecto y la configuración del mismo.

En gvSIG, cuando se guarda un proyecto, se recorre el árbol de componentes del proyecto para solicitar su configuración en formato XML. De estos componentes, los que sean de tipo Metadatable, deberán incluir sus Metadatos en su configuración de proyecto a persistir.

Será conveniente proporcionar un mecanismo genérico que permita generar la configuración XML correspondiente a partir de un Metadato. También se incluirá el mecanismo inverso, que permita construir un Metadato a partir de la configuración de proyecto.

De este requerimiento surgen una serie de casos de uso:

diagramas/casos-uso-rq1_1-img

Casos de uso de persistencia de Metadatos.

Note

DEFINIR

Requerimiento RQ1.2: Registro de Metadatos

Se incorporará a gvSIG un servicio centralizado de registro de Metadatos, en el que se guarden y consulten la definición de los Metadatos de gvSIG.

Un registro de Metadatos (wikipedia) es un repositorio centralizado de definiciones de Metadatos, dónde se almacenan y mantienen de forma controlada. Una definición de Metadato especifica el formato del mismo, los atributos o Elementos que lo componen y la descripción y tipo de datos de cada uno de ellos.

Este registro permitirá que, cualquier componente, registre de forma dinámica los Metadatos o Elementos que aporta a la jerarquía de Metadatos de gvSIG. Además permitirá también la consulta de las definiciones de Metadatos disponibles, así como de los Elementos y tipos de datos que los constituyen.

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:

diagramas/entidades-img

Diagrama de entidades

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.

diagramas/contexto-img

Diagrama de contexto


Subsistemas Metadatable

Descripción

Todo componente de gvSIG al que se pueden asociar Metadatos. Por ej: Proyecto, Documento, Capa, etc.

Responsabilidades:

MDManager

Introducción

Se encarga de la creación y serialización de Metadatos, acorde a su definición.

Responsabilidades:

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:

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:

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:

Sería interesante que soportara también transacciones en la modificación de los Metadatos.


Diseño
Implementación

Describir cómo serían los procesos de:

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
diagramas/modelo-datos-img

Modelo de datos de los Metadatos y sus definiciones

Temas pendientes

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.


subdriver


Otras librerias


UI Components

Índice e Introducción

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:


ControlesBasicos
JButton

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.

imagenes/JButton1.JPG

Se observa el tamaño estándar de un botón y como ajusta el ancho para otro texto más extenso.

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
GridBagLayoutPanel

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.

imagenes/GridBagLayoutPanel1.JPG

Ejemplo de utilización de la clase GridBagLayoutPanel.

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));
JOptionsEditionByMousePopupMenu

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:

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.

imagenes/JOptionsEditionByMousePopupMenu1.JPG

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

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.

imagenes/NumberRange1.JPG

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);
TextIncreaser

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.

imagenes/TextIncreaser1.JPG

Ejemplo de la utilización de la clase.

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

tic1.setValue(2.5);

Y por último añadimos el panel al frame

frame.getContentPane().add(tic1);
JTextPreview

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.

imagenes/JTextPreview1.JPG

Ejemplo con texto plano.

Y otro de como mostraría la misma cadena con formato ITALIC.

imagenes/JTextPreview2.JPG

Ejemplo con texto en negrita (BOLD)

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

content.add(txt);
JDecimalField

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.

imagenes/JDecimalField1.JPG

En la imagen se observa el resultado de crear un JDecimalField.

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

test.setScale(3,7);

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:

DataInputContainer y CoordDataInputContainer

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.

imagenes/DataInputContainer1.JPG

Imagen de un ejemplo de utilización de la clase DataInputContainer.

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

data.setCaracter(false);

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.

imagenes/CoordDataInputContainer1.JPG

CoordDataInputContainer.

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);
DateComboBox

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

imagenes/DateComboBox1.JPG

DateComboBox. Aparecen los ComboBox sin desplegar.

imagenes/DateComboBox21.JPG

DateComboBox. Se muestra el PopUp que aparece cuando desplegamos el ComboBox

imagenes/DateComboBox3.JPG

DateComboBox. Se muestra como queda el ComboBox despues de seleccionar una fecha en el PopUp

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)
JCalendarCDatePanel y JCalendarDatePanel

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.

imagenes/JCalendarCDatePanel1.JPG

JCalendarCDate. Ejemplo de uso con el calendario no desplegado.

imagenes/JCalendarCDatePanel2.JPG

JCalendarCDate. Ejemplo de uso con el calendario desplegado.

imagenes/JCalendarDatePanel1.JPG

JCalendarDate. Ejemplo de uso con el calendario no desplegado.

imagenes/JCalendarDatePanel2.JPG

JCalendarDate. Ejemplo de uso con el calendario desplegado.

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);
JCalendarDateDialog

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.

imagenes/JCalendarDateDialog1.JPG

Se observa el botón que utilizamos para invocar al diálogo que contiene al calendario.

imagenes/JCalendarDateDialog2.JPG

En la imágen se puede ver el diálogo que se crea cuando pulsamos el botón.

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);
ComboBoxItemSeeker

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):

Comportamiento de búsqueda (Search Behavior): Configura cómo devolverá un objeto al realizar una búsqueda:

Distinguir entre mayúsculas (Case Sensitive): Distingue entre mayúsculas y minúsculas a la hora de realizar la búsqueda:

Por defecto la clase está configurada de la siguiente manera:

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:

imagenes/ComboBoxItemSeeker1.JPG

En esta primera imagen se muestran todos los objetos introducidos según una configuración inicial.

imagenes/ComboBoxItemSeeker2.JPG

En la segunda imágen se muestran los objetos ordenados y que comiencen por el carácter "a".

imagenes/ComboBoxItemSeeker3.JPG

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);
ComboScale

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.

imagenes/ComboScale2.JPG

Aparece, en el programa de test, el valor actual de la escala.

imagenes/ComboScale1.JPG

Aparece, en el programa de test, el ComboBox desplegado con los posibles valores para la escala.

imagenes/ComboScaleGVSIG1.JPG

Ejemplo de uso en gvSIG con el valor actual de escala.

imagenes/ComboScaleGVSIG2.JPG

Ejemplo de uso en gvSIG con el ComboBox desplegado con los valores de escala.

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

cs.setScale(400);

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
ComboButton

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.

imagenes/ComboButton1.JPG

Aparece el estado inicial del ComboButton del ejemplo.

imagenes/ComboButton2.JPG

Aparece el ComboButton desplegado.

imagenes/ComboButton3.JPG

Se muestra el ComboButton con el estado cambiado.

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");

ControlesCompuestos
JDnDList

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.

imagenes/JDnDList1.JPG

Imágen inicial en la que se muestran dos listas.

imagenes/JDnDList2.JPG

En esta imágen se muestra como ha quedado la lista tras reordenarla mediante Drag and Drop.

imagenes/JDnDList3.JPG

En esta imágen se observa como hemos añadido elementos de una lista a otra y las hemos reordenado.

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

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.

imagenes/MultiLineToolTip1.JPG

Tip de varias líneas.

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);
FilterButtonsPanel

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.

imagenes/FilterButtonsPanel.JPG

Ejemplo del panel creado.

El código para crear el panel es

FilterButtonsJPanel panel=new FilterButtonsJPanel();
content.add(panel);
FilterQueryJPanel

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.

imagenes/FilterQueryPanel1.JPG

Se observa el panel que crea esta clase con los campos a derecha, izquierda y a bajo y lo botones.

imagenes/FilterQueryPanelGVSIG1.JPG

Ejemplo de utilización de la clase 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.

imagenes/TableFilterQueryJPanel1.JPG

Ejemplo de utilización de la clase TableFilterQueryQueryJPanel.

El código necesario para crear el panel es el siguiente

Constructor para crear el panel

FilterQueryJPanel filterQueryJPanel = new FilterQueryJPanel();
TreeTable

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:

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.

imagenes/TreeTable1.JPG

Árbol comprimido.

imagenes/TreeTable2.JPG

Árbol totalmente desplegado.

imagenes/TreeTable3.JPG

Imágen que muestra lo que sucede cuando se selecciona un nodo padre.

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);
TreeListContainer

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.

imagenes/TreeListContainer.JPG

Ejemplo de utilización de la herramienta. En el ejemplo se ha incorporado algún elemento a la lista.

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);
AcceptCancelPanel

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.

imagenes/AcceptCancelPanel1.JPG

Ejemplo de panel creado con los botones en la disposición y tamaño estadarizado.

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. ");
       }
 };
ButtonBar

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.

imagenes/ButtonBar.JPG

Ejemplo de la clase ButtonBar

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
Pager

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.

imagenes/Pager1.JPG

En la imágen se muestra el conjunto de controles que implementa 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

f.setContentPane(pag);
SliderTextContainer

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.

imagenes/SliderTextContainer1.JPG

Imágen descriptiva de la herramienta.

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);
GraphicPanel

El conjunto de clases de este componente está formado por:

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.

imagenes/GraphicPanel1.JPG

Muestra el ejemplo creado para la prueba de la herramienta.

imagenes/GraphicPanel2.JPG

Mensaje PopUp que aparece cuando se pulsa el botón derecho.

imagenes/GraphicPanel3.JPG

Cuadro para modificar las preferencias de la gráfica.

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.

imagenes/GraphicPanel5.JPG

Imágen del panel con los controles de tipo slider.

La herramienta muestra por defecto la siguiente gráfica.

imagenes/GraphicPanel4.JPG

Gráfica mostrada por defecto.

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);
OpenFileContainer

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.

imagenes/OpenFile1.JPG

Ejemplo del control creado con un botón que llamará al diálogo para seleccionar fichero.

imagenes/OpenFile2.JPG

Ejemplo del diálogo creado cuando se pulsa el botón.

imagenes/OpenFile3.JPG

Ejemplo del TextField con el nombre del fichero seleccionado.

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);
TableContainer

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:

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.

imagenes/TableContainerList.JPG

Ejemplo de tabla con modelo "Lista"

imagenes/TableContainerCheckBox1.JPG

Ejemplo de tabla con modelo "CheckBox"

imagenes/TableContainerButtons.JPG

Ejemplo de tabla con modelo "Buttons"

imagenes/TableContainerRadioButton1.JPG

Ejemplo de tabla con modelo "RadioButtons"

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

table.initialize();

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);

Apéndice
Messages

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.


Apéndices


Como se genera una distribucion de binarios de gvSIG

Proceso de generación

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:

Los desarrolladores trabajan en dos lineas paralelas (ramas) sobre el sistema de gestión de fuentes (actualmente SVN):

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:

images/linux_distribucion_proceso.png
  1. Preparamos la carpeta de instalación copiando las librerias y extensiones del proyecto.
  2. Usando el IzPack obtenemos el gvSIG.jar (siguiendo el fichero instalar.xml).
  3. Con el jar y el launcher preparamos el directorio temporal.
  4. Se comprime el directorio temporal (usando tar o 7z) para obtener el fichero tmp. Y finalmente,
  5. 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.

Paquete de instalación final

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.

images/linux_distribucion_paquete.png

Como configurar el Geonetwork para que funcionen los acentos desde gvSIG

Creación de una base de datos en MySQL 5.0 en UTF-8

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.

Configuración del Tomcat 5.5.23 en UTF-8

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"/>
Instalación y Configuración de Geonetwork 2.0.3

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&amp;characterEncoding=utf-8&amp;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;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.


Problemas conocidos
Transformation Exception

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.

No funcionan las búsquedas remotas con acentos desde el cliente web

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í


Cómo ejecutar un Geonetwork desde Tomcat y visualizarlo en el Eclipse en modo debug

Descripción detallada de los pasos a seguir

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.

TEXTO

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.

TEXTO

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.

TEXTO

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.


Como se genera una distribucion de fuentes de gvSIG

Proceso de generacion

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:

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:

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:

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:

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).