MapControl
Componente diseñado para simular la interacción con las distintas capas con información gráfica, mediante herramientas que el usuario pueda manejar.
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:
- ACTUALIZADO / UPDATED : refresca la pantalla con el contenido del buffer. Es proceso de pintado rápido.
- DESACTUALIZADO / OUTDATED : actualizar toda la información visual de todas las capas visibles y disponibles. Puede ser el proceso más pesado de pintado.
- ONLY_GRAPHICS : actualizar capa/s de simbología y geometrías.
Proceso de pintado
El proceso de pintado de MapControl sigue el siguiente algoritmo:
- Si el status es ACTUALIZADO:
- Si hay doble-buffer:
- Si hay un Behavior para gestionar la instancia de MapControl: delega el proceso de dibujado a dicho Behavior, invocando: behavior_instance.paintComponent(g).
- Sino, repinta rápidamente la información gráfica actual, invocando: g.drawImage(image,0,0,null).
- Si hay doble-buffer:
- 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.
- Si hay doble-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
- Si MapControl tiene double-buffer donde pintar:
- 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:
- 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 .
Diagrama
El diagrama 1 muestra los principales elementos que intervienen con MapControl.
Pulse aquí para ver el diagrama en grande.
Descripción del diagrama
- MapControl es un componente Swing que implementa:
- ComponentListener para atender eventos producidos al moverlo, ocultarlo, redimensionarlo, o mostrarlo. En realidad estos métodos no están implementados, sino que se han dejado por si alguna subclase los necesitase.
- CommandListener para atender eventos producidos por la ejecución de operaciones de edición de capas.
- Contiene un doble-buffer propio sobre el que realizar el proceso de pintado: se dibuja en el back-buffer, y una vez completado, o cada cierto tiempo si el proceso es largo, se actualiza el front-buffer con el back-buffer.
- Crea un objeto (CancelDraw) compartido de tipo Cancellable con el que podrá notificar a todas las capas y MapContext si pueden continuar el proceso de dibujado, o deben cancelarlo.
- Las distintas capas con información gráfica las almacena en un objeto de tipo MapContext, que servirá para gestionarlas, gestionar los eventos (agrupados en AtomicEvent) que se producen en ellas mediante un buffer (EventBuffer), proyectarlas según una proyección y el puerto de vista (ViewPort) disponible, y controlar su dibujado: calidad, ...
- Contiene una serie de identificadores que definen su posible comportamiento, cada identificador asociado a un Behavior o una composición de estos. En un momento dado, tendrá como currentMapTool, sólo uno de estos comportamientos posibles.
- Define un listener: MapToolListener, que será el encargado de procesar cualquier evento de ratón que se produzca en MapControl, y notificarlo a la herramienta activa: currentMapTool. Para el caso del evento por rueda del ratón: MouseWheelEvent, solo notifica un evento por segundo, para dar tiempo a que se pueda cancelar el proceso anterior.
- La herramienta activa delega el control a la ToolListener asociada, enviándole un evento con la información necesaria para que realice dicho procesamiento. Cualquier excepción durante dicho procesamiento implicará el lanzamiento de alguna BehaviorException.
- BehaviorException es una simple excepción de Java, que sirve para identificar un problema producido en la ejecución del código de un Behavior.
- Toda excepción producida atendiendo una petición de pintado, trabajando con la herramienta activa, o, ejecutando alguna de las operaciones soportadas por defecto por MapControl: zoom in, o zoom out, será lanzada por un objeto de tipo ExceptionHandlerSupport a las ExceptionListener que estuviesen registradas.
- Todos los eventos (de tipo AtomicEvent) que pueda lanzar el objeto interno de tipo MapContext serán capturados y procesados por un listener de tipo MapContextListener definido en MapControl.
- Siempre que es invocado el método protected void paintComponent(Graphics g) de MapControl, y su estatus es DESACTUALIZADO, o ONLY_GRAPHICS, se creará una nueva petición de pintado: PaintingRequest, que se le notificará el Drawer2, objeto encargado de gestionarlas.
- El objeto interno de tipo Drawer2 posee un hilo trabajador (Worker) que es el que realiza el pintado implementado en la petición (PaintingRequest). Drawer2 sólo almacena 2 peticiones de pintado, la que está siendo atendida, y una en cola:
- Si llega una nueva petición, se borra la que estaba en cola.
- Si el hilo trabador acaba, intenta obtener la petición en cola, en caso que no hubiese, pasa a espera pasiva.
- Estando el hilo trabajador en espera pasiva, si llega una petición de pintado, se notifica al hilo para que vuelva a ejecución.
- Contiene un temporizador (Timer) que utilizará para refrescar el front-buffer del double-buffer cada 360 ms. con la información del back-buffer, mientras se está ejecutando un proceso pesado de pintado.
Funcionalidad
- Cancelación del pintado: permite cancelar el proceso actual de pintado en cualquier momento.
- Componente Java de GUI: el desarrollador puede incorporar MapControl en la interfaz gráfica de su aplicación como otro JComponent más.
- Doble buffer: obtener el doble-buffer con el que pinta las capas.
- Herramientas:
- Agregar un Behavior, o un conjunto (que será un CompoundBehavior), identificado por un String, como herramienta con las que se podría interactuar con el objeto MapControl.
- Buscar una herramienta registrada, vía su identificador.
- Obtener todas las herramientas registradas, junto con su identificador.
- Obtener sólo los identificadores de las herramientas registradas.
- Establacer una de las herramientas registradas, como activa, de modo que definirá el comportamiento de MapControl.
- Obtener la herramienta activa, o su identificador.
- Averiguar si hay una determinada herramienta registrada.
- Establecer como activa, la que previamente lo estuvo.
- Listeners:
- ExceptionListener : agregar, o eliminar este tipo de listener, o notificar a todos estos listeners registrados de alguna excepción producida:
- Atendiendo una petición de pintado.
- Trabajando con la herramienta activa.
- O, ejecutando alguna de las operaciones soportadas por defecto por MapControl: zoom in, o zoom out .
- ExceptionListener : agregar, o eliminar este tipo de listener, o notificar a todos estos listeners registrados de alguna excepción producida:
- MapContext: establecer u obtener un objeto MapContext. Las distintas capas con información gráfica las almacena en un objeto de tipo MapContext, que servirá para gestionarlas, gestionar los eventos (agrupados en AtomicEvent) que se producen en ellas mediante un buffer (EventBuffer), proyectarlas según una proyección y el puerto de vista (ViewPort) disponible, y controlar su dibujado: calidad, ...
- Pintado: ofrece distintos mecanismos de pintado ( vea `Pintado de MapControl`_ ):
- Forzar pintado completo de todas las capas (cancelando el pintado previo que estuviese en marcha):
- Pintado completo de todas las capas: drawMap(false).
- Pintado completo de todas las capas, vaciando el front-buffer con el color de fondo del puerto de vista, o a blanco, si no estuviese definido: drawMap(true).
- Pintar sólo la capa de tipo GraphicLayer existente: drawGraphics().
- Repintar solo las capas que no estén actualizadas (capas sucias) (cancelando el pintado previo que estuviese en marcha): rePaintDirtyLayers().
- Forzar pintado completo de todas las capas (cancelando el pintado previo que estuviese en marcha):
- Proyección: obtener la proyección actual en que se está mostrando la información gráfica de las capas.
- Puerto de vista: obtener el puerto de vista utilizado para ajustar el extent seleccionado de las capas, al disponible.
- Zoom: MapControl implementa por defecto las operaciones de zoom in, y zoom out.
Enlaces de interés
- Behavior: descripción de los comportamientos básicos.
- Eventos: tipos de eventos que pueden recibir las ToolListener.
- FLayers: las distintas capas con información gráfica que puede contener MapContext.
- MapContext: lógica de gestión y pintado de las capas.
- MapOverview : MapControl que se puede utilizar como vista en miniatura de otro.
- ToolListener: listeners que completan la simulación de una herramienta con la que interactuar con MapControl.