Constantino Sánchez Ballesteros

Dpto. Informática de Sgrin, S.A. – Grupo CADIC

 

Visor Web v8.3.14.0

Detalles sobre compatibilidad, implementación y arquitectura

En esta nueva versión se ha reprogramado desde cero todo el soporte cliente del visor (JavaScript). El objetivo principal ha sido ofrecer un diseño más flexible y organizado, pensando siempre en el desarrollador final.

 

Actualmente se soportan los navegadores más populares. Se ha procurado que el visor se comporte de manera similar en todos ellos, aunque hay que ser conscientes que cada uno tiene sus peculiaridades y es complicado lograr una funcionalidad exactamente igual para todos. Hemos testeado sobre los siguientes navegadores y siempre bajo entorno Windows:

 

  1. Internet Explorer 7.0.5730.13
  2. Internet Explorer 6.0.3790.3959
  3. Mozilla Firefox 2.0.0.12
  4. Opera 9.25
  5. Safari 3.0.4 (Apple)

 

En principio, el API cliente del visor Web es compatible con los desarrollos de versiones anteriores, aunque a partir de ahora es preferible utilizar los nuevos métodos que ofrece para estar en sintonía con las características actuales y futuras.

 

El diseño visual del visor se basa capas (DIVs en HTML) que tratan la información de forma discriminada. Actualmente están implementadas las siguientes:

 

 

La capa Raster, que utiliza el formato JPEG, está íntimamente ligada al modo de Scaling que se aplique al visor (propiedad de servidor del control). Scaling.Free permite hacer ajustes de ventana y escalados precisos, sin embargo no aprovecha la característica de la caché de disco que ofrecen los Raster con LatinoServer. Scaling.PowerOfTwo ajusta siempre el zoom entre niveles, siendo algo menos preciso pero mejorando muchísimo la velocidad a la que se pueden obtener imágenes Raster.

 

Nota: Para habilitar la caché de disco en proveedores Raster hay que configurarlo desde LatinoServer.

 

La capa vectorial hace uso del formato GIF con soporte de transparencia. Ahora se verá mucho más nítida y “limpia” que en versiones anteriores puesto que el Bitmap generado no se mezcla con el Raster.

 

La capa de iconos mostrará éstos en base a los proveedores que tengan este elemento como dato cartográfico y la de chinchetas dependerá de los elementos que se añadan desde la colección Tacks del visor o desde el cliente mediante llamadas al API.

 

Por último, las capas personalizadas permiten agregar cualquier tipo de elemento soportado por HTML. Se pueden utilizar simples imágenes o incluso utilizarlas para dibujar elementos vectoriales. Para este último caso el visor tiene un nuevo soporte añadido que veremos más adelante.

 

Migración a la nueva versión

Los que actualicen proyectos a esta nueva versión del visor deberían tener presente un par de consideraciones para evitar errores de compilación y en tiempo de ejecución en el cliente.

 

Puesto que Visual Studio serializa los valores de las propiedades de los controles en tiempo de diseño, es seguro que no reflejen los nuevos cambios aunque referenciemos el nuevo ensamblado de OLatino. Para solventar este problema la mejor opción es quitar de las páginas los botones y visores de OLatino y volverlos a insertar y conectar, si corresponde, sus eventos. Si todos los controles se han añadido de forma programática la migración será transparente en este sentido.

 

Del lado del servidor, las propiedades asignadas al visor o a botones, y que no se reconozcan en tiempo de compilación, es aconsejable eliminarlas aunque todo compile correctamente. Esto no será necesario si quitamos y ponemos de nuevo los controles en las páginas ASP.

 

LtnViewerPlot: Visor ligero de OLatino

El visor Web siempre se ha basado internamente en un objeto LtnViewer para realizar los “ploteos” gráficos. En esta nueva versión se ha implementado uno nuevo (LtnViewerPlot) mucho más ligero y que internamente se ha optimizado exclusivamente para ploteos, ganando principalmente en gestión de recursos. Por lo tanto, ahora la propiedad ViewerHandler requiere un visor de este nuevo tipo.

 

Arquitectura: Comportamientos y controladores

Entrando de lleno en la arquitectura interna del visor hay que destacar dos elementos importantes:

 

 

Uno de los objetivos al desarrollar esta nueva versión era separar claramente la funcionalidad del visor y los responsables de aprovechar esta funcionalidad. Para ello, inspirado en el patrón Model/View/Controller se creó el sistema de comportamientos y controladores.

 

Un comportamiento se encarga exclusivamente de interaccionar con el visor para que éste visualice un resultado y no tiene conocimiento de el/los controladores que lo manipulan . Algunos ejemplos:

 

Ø      Realizar un FitView

Ø      Navegación multidireccional

Ø      Zoom ventana y dinámico

Ø      Realizar Panning

 

Un controlador hace uso de un comportamiento y lo manipula en base a entradas de teclado, ratón, eventos, etc. Con esto quiero dejar claro que un comportamiento no hará nada si no lo ordena ‘alguien’ (un controlador, código propio del desarrollador en respuesta a un evento, etc.).

 

Para dar más potencia y flexibilidad se ha añadido soporte para incluir comportamientos personalizados en el cliente, que veremos más adelante cómo programarlos.

 

Desarrollo basado en el visor Web

En los siguientes apartados se irán describiendo diferentes tareas muy utilizadas a la hora de desarrollar aplicaciones bajo el dominio del visor. Por supuesto que la información aquí reflejada sirve como un complemento a la documentación que actualmente estamos desarrollando y no detalla todos los métodos disponibles. Aún así, servirá de punto de partida para evitar problemas tempranos en el desarrollo...

 

Inicialización

El visor contiene dos métodos que nos permiten tener notificación de cuándo está totalmente operativo u ocurrió un error de carga al iniciarse. Si escribimos un manejador en el evento onload del body de la página nos quedaría un código similar a éste (LtnWebViewer1 es el identificador que he utilizado como ID del visor en tiempo de diseño):

 


<body onload="load();">

 

<script>

    function load()

    {

        LtnWebViewer1.notifyLoad(onVisorloaded);

        LtnWebViewer1.notifyError(onVisorError);

    }

    function onVisorloaded() {}

    function onVisorError() {}

</script>

 

El manejador onVisorloaded será el punto inicial en el que podremos comenzar a suscribirnos a eventos del visor o realizar alguna tarea determinada sobre él.

 

Eventos

El visor soporta un número determinado de eventos a los que podemos suscribirnos o eliminar la suscripción con los siguientes métodos:

 

subscribeEvent (evtType, callback)

unsubscribeEvent (evtType, callback)

 

evtType es un enumerado que indica el evento sobre el que se realizará la suscripción. callback es la función que lo manejará. Los eventos posibles para evtType son los siguientes:

 

Ø       WVEventType.DOUBLE_CLICK

Ø       WVEventType.LEFT_CLICK

Ø       WVEventType.RIGHT_CLICK

Ø       WVEventType.MOUSE_MOVE

Ø       WVEventType.CHANGE_SCALE

Ø       WVEventType.CHANGE_BEHAVIOR

Ø       WVEventType.ICON_CLICK

Ø       WVEventType.ICON_OVER

Ø       WVEventType.ICON_OUT

 

Comportamientos y controladores de sistema

Por defecto, el visor tiene activados todos los controladores y comportamientos para tener una funcionalidad completa. De hecho, los botones que vienen de fábrica con el visor acceden internamente a la funcionalidad proporcionada por esos comportamientos. Aunque no es lo normal, a veces se precisa prescindir de cierta funcionalidad porque no es necesaria para la aplicación que se está desarrollando. Por ello es posible deshabilitar controladores o incluso eliminar comportamientos en tiempo de ejecución desde el cliente.

 

Por ejemplo, existe un comportamiento que muestra las coordenadas UTM correspondientes al puntero del ratón en la barra de estado del navegador. Si no se desea esta característica, podemos hacer lo siguiente dentro de la notificación de inicialización del visor:

 

function onVisorloaded()

{    

LtnWebViewer1.removeSystemBehavior(WVBehaviorType.UTMCOORDS);

}

 

La lista de tipos de comportamientos de sistema es la siguiente:

 

Ø       WVBehaviorType.PANNING: soporte de panning

Ø       WVBehaviorType.ZOOM_POWER_TWO: zoom progresivo entre niveles. Utilizado por los botones de Zoom +/-

Ø       WVBehaviorType.NAVIGATOR: navegación direccional. Utilizado por la rueda de navegación incrustada a la derecha del visor.

Ø       WVBehaviorType.HISTORY_BROWSER: histórico de localizaciones. Utilizado por los botones de localización anterior y siguiente.

Ø       WVBehaviorType.FENCE: zoom ventana. Utilizado por el botón de zoom interactivo.

Ø       WVBehaviorType.DISTANCE: medir distancias. Utilizado por el botón de medir distancias.

Ø       WVBehaviorType.AREA: igual que el anterior pero para áreas.

Ø       WVBehaviorType.FIT: realiza una visualización de toda la cartografía. Utilizado por el botón FitView.

Ø       WVBehaviorType.UTMCOORDS: muestra coordenadas UTM en la barra de estado del navegador. No es utilizado por ningún botón.

Ø       WVBehaviorType.CARTINF: muestra ventana con información cartográfica del elemento más cercano. Utilizado por el botón InfoCart.

Ø       WVBehaviorType.ALPHINF: igual que el anterior pero muestra información enlazada a base de datos.

Ø       WVBehaviorType.PRINT: permite la impresión de la vista actual. Utilizado por el botón Print.

Ø       WVBehaviorType.REFRESH: refresca la vista. Utilizado por el botón Refresh.

Ø      WVBehaviorType.EMPTY: comportamiento comodín que no hace nada. Se puede considerar como el comportamiento base para todos los anteriores.

 

En el caso de los controladores podemos habilitar o deshabilitar su funcionalidad en cualquier momento. Esta acción permite realizar un bloqueo total del visor que puede ser interesante cuando se ejecutan ciertas herramientas personalizadas. Por ejemplo, si hemos programado una herramienta que permite dibujar un cercado sobre el visor, no es deseable que cuando se hace click sobre el visor se active a su vez el panning o haya otro efecto colateral con el uso del ratón. Realizando un bloqueo podemos seguir recibiendo eventos y no tendremos este tipo de problemas. Los métodos que permiten este soporte son:

 

Ø       lockControllers()

Ø       unlockControllers()

 

Comportamientos personalizados

Ya comenté anteriormente que podemos agregar al visor, en tiempo de ejecución, comportamientos personalizados que realicen una funcionalidad bien definida. Para construir uno es obligado seguir el siguiente esqueleto (podría interpretarse como una interfaz, salvando las distancias):


    function MiComportamiento()

    {

        var enabled = true; //activado por defecto

       

        this.getEnabled=function()

        {

            return enabled;

        }

       

        this.setEnabled=function(enable)

        {

            enabled = enable;

        }              

       

        this.onUpdate=function(deltaTime) //notifica actualización del comportamiento

        {

        }

       

        this.dispose=function() //es llamado cuando se elimina el comportamiento

        {

        }

    }

 

onUpdate se ejecutará de forma continua siempre que enabled tenga el valor true. deltaTime indica el tiempo que ha pasado, en milisegundos, desde la última actualización.

 

Para agregar un comportamiento de este tipo podemos utilizar el siguiente ejemplo:

 

  var beh = new MiComportamiento ();       

        LtnWebViewer1.addCustomBehavior(beh);

 

Los métodos para agregar o quitar comportamientos propios son:

 

Ø       addCustomBehavior (b)

Ø       removeCustomBehavior (b)

 

En el siguiente ejemplo se muestra un sencillo comportamiento que gestiona el evento doble click del visor para centrar la vista sobre la coordenada seleccionada:


    function MiComportamiento()

    {

        var enabled = true;

        var viewer = null; //puntero al visor manipulado

       

        this.getEnabled=function()

        {

            return enabled;

        }

       

        this.setEnabled=function(enable)

        {

            enabled = enable;

        }

       

        this.init=function(v)

        {

            viewer = v;

            //suscripción a dobleclick del visor

            viewer.subscribeEvent(WVEventType.DOUBLE_CLICK, onDoubleClick);

        }

       

        function onDoubleClick(visor, utmX, utmY)

        {

            if (enabled)

            {

                //posición central del visor en UTM

                var vCenter = visor.getCenterUtm();

                //posición central en píxels

                var pxCenter = visor.transformWorldToLocal(vCenter.x, vCenter.y);

                //posición de destino en píxels

                var px=visor.transformWorldToLocal(utmX, utmY);

                //desplaza la vista desde el centro al destino

                visor.move(pxCenter.x - px.x, pxCenter.y - px.y);

            }

        }

       

        this.onUpdate=function(deltaTime){ //Do nothing ;-) }

       

        this.dispose=function()

        {

            //elimina suscripción a evento

            viewer.unsubscribeEvent(WVEventType.DOUBLE_CLICK, onDoubleClick);

        }

    }

 

Para instanciar este comportamiento y agregarlo al visor es conveniente hacerlo cuando éste esté inicializado. Para ello aprovechamos el manejador function onVisorloaded() {} que se describió en un apartado anterior:

 

  var beh = new MiComportamiento();

        beh.init(LtnWebViewer1);

        LtnWebViewer1.addCustomBehavior(beh);

 

El método onUpdate, aunque en este comportamiento no se utilice, es útil cuando necesitamos realizar actualización constante (basado en el tiempo) de algún objeto o dato. Por ejemplo, se podría mejorar el comportamiento anterior para que la transición al punto de destino la hiciera interpolando. En este caso onUpdate lo utilizaríamos para aplicar una parte de la interpolación (basada en el tiempo marcado por el parámetro deltaTime) hasta que se alcanzara finalmente el punto de destino.

 

Novedades del minivisor

El control LtnWebMiniViewer se utiliza normalmente para tener una vista global y un rápido acceso a la cartografía mostrada por un visor normal. A partir de esta versión la gestión del contenido renderizado en él será a cargo del programador de aplicaciones. Las principales propiedades y métodos para trabajar con el minivisor son las siguientes:

 

Ø      BackgroundImage : propiedad de tipo Bitmap que se utiliza para asignar el fondo gráfico del minivisor.

Ø      LinkViewer: propiedad para asignar el visor web principal con el que interaccionará el minivisor.

Ø      SetBounds : método que establece los límites de visualización. Normalmente se asignará el tamaño (bounds) del proveedor o proveedores a mostrar.

 

En el siguiente ejemplo se muestra una función que permite obtener un Bitmap a partir de un LtnViewerPlot:

 

Bitmap RenderBitmap(LtnViewerPlot vp, LtnWindow winOrigen, Rectangle  winDestino)

    {

        Bitmap bmp = new Bitmap(winDestino.Width, winDestino.Height);

        Graphics gfx = Graphics.FromImage(bmp);

        vp.StartPlot(gfx);

        vp.Plot(winOrigen, winDestino);

        vp.EndPlot();

        gfx.Dispose();

        gfx = null;

        return bmp;

    }

 

vp representa un visor que tiene en su propiedad Sources uno o varios proveedores agregados. winOrigen representa los bounds en coordenadas mundo UTM y winDestino representa el tamaño del minivisor en pixels.

 

Trabajo con capas

Capas de sistema

En la nueva arquitectura el trabajo con capas se ha intentado simplificar en la medida de lo posible. Podemos apagar y encender capas de sistema (Raster, Vectorial e Iconos) desde JavaScript. Además, si alguna capa se apaga, no se pedirán datos gráficos de ella al servidor; esto implica ahorro de conexiones, ancho de banda, mejora de rendimiento al manejar menos imágenes, etc.

 

Veamos un ejemplo de cómo podemos mostrar u ocultar la capa Raster desde el cliente:

 

var layer = LtnWebViewer1.getSystemLayer(WVLayerType.RASTER); layer.setVisible(!layer.getVisible());

 

El enumerado de tipos de capas de sistema es el siguiente:

 

Ø       WVLayerType.RASTER

Ø       WVLayerType.VECTOR

Ø      WVLayerType.ICON

 

Otro uso interesante de las capas es la posibilidad de refrescar su vista, lo que implica pedir datos gráficos al servidor. Ésta es una operación muy utilizada sobre todo en la capa vectorial del visor Web. Es la forma de reflejar en el cliente cambios realizados en el servidor como apagado o encendido de capas en proveedores, actualización de un mapa temático, etc.

 

LtnWebViewer1.refreshSystemLayer(WVLayerType.VECTOR);

 

Capas personalizadas

Las capas personalizadas nos permiten agregar elementos propios de HTML al visor Web. Actualmente éstos elementos se agregan con alineación al centro respecto a su propio tamaño. Estas capas son útiles principalmente para agregar elementos puntuales como iconos propios. Veamos un ejemplo:

 

    var layer = LtnWebViewer1.createCustomLayer("test");

    var img = document.createElement('IMG');

    img.style.width="30";

    img.style.height="30";

    img.src="smile.gif";       

    layer.addItem(img, LtnWebViewer1.getCenterUtmX(), LtnWebViewer1.getCenterUtmY());

 

Para crear físicamente la capa en el visor utilizamos el método createCustomLayer. En este caso insertaremos un elemento imagen, el cual se crea de la forma habitual mediante el DOM del navegador. Finalmente, para agregar la imagen a la capa utilizamos el método addItem, que en este ejemplo usa como coordenadas mundo el punto central del visor Web.

 

Hecho! No hace falta suscribirse al evento de cambio de escala del visor ni hacer nada más, ya que es la propia capa la que gestiona internamente los ítems agregados (drawing, scrolling, etc.).

 

A continuación se detallan los métodos más importantes de las capas personalizadas:

 

Ø      createCustomLayer(id) : crea una capa personalizada en base a un identificador de tipo string. Retorna la instancia correspondiente.

Ø      removeCustomLayer(obj) : elimina una capa personalizada. El parámetro debe ser la instancia retornada por el método createCustomLayer.

Ø      getContainer() : retorna el contenedor de la capa (es un DIV de HTML).

Ø      setAutoClean(bool) : true (por defecto) si queremos que la capa gestione automáticamente el repintado de elementos (por cambios de escala, etc.). false en caso contrario.

Ø      clear() : elimina todos los items de la capa.

Ø      addItem(obj, utmX, utmY) : agrega un item (HTML DOM object) a la capa y lo coloca en base a las coordenadas UTM.

Ø      setAllScaleVisible(bool) : establece si se mostrará la capa en todas las escalas. Si el valor es falso se tendrá en cuenta el valor asignado mediante el método setScaleMaxVisible.

Ø      setScaleMaxVisible(int) : establece la escala máxima de visualización de la capa.

 

Canvas para dibujo vectorial

El visor Web tiene un soporte interesante para el pintado de gráficos vectoriales desde el cliente. Para aprovecharlo se precisa de una capa personalizada y un objeto “pintor”:

 

var layer = LtnWebViewer1.createCustomLayer("canvas"); //crea capa personalizada

layer.setAutoClean(false); //gestionamos manualmente el repintado

      var painter = LtnWebViewer1.createLayerPainter(layer); //crea pintor

      painter.setColor("00ff00"); //asigna color de brocha

      painter.setStroke(2); //asigna tamaño de brocha

 

painter.clear(); //limpia el canvas

painter.fillPolygon(lstUtmX, lstUtmY); //pinta polígono

painter.paint(); //muestra el resultado en la capa

 

Las capas personalizadas actualmente sólo gestionan de forma automática el manejo de objetos simples (imágenes, etc.). Si se utilizan para dibujar sobre ellas, es necesario quitar el borrado automático con setAutoClean y gestionar nosotros mismos el repintado de elementos. También será necesario suscribirse al evento WVEventType.CHANGE_SCALE para repintar en cada notificación si queremos mantener el dibujo vectorial entre cambios de escala.

 

Internamente se hace uso de una excelente librería externa llamada wz_jsgraphics (el visor utiliza la versión v3.03) para el dibujado vectorial desde el cliente. Para tener información ampliada acerca de su utilización se puede visitar su Web: http://www.walterzorn.com

 

Trabajo con iconos

Si algún proveedor agregado al visor contiene iconos, éstos se mostrarán como objetos IMG independientes. Para poder asignarles alguna información a su tooltip o incluso gestionar su evento click, podemos suscribirnos a eventos del siguiente modo:

 

LtnWebViewer1.subscribeEvent(WVEventType.ICON_CLICK, iconClick);

      LtnWebViewer1.subscribeEvent(WVEventType.ICON_OVER, iconOver);

      LtnWebViewer1.subscribeEvent(WVEventType.ICON_OUT, iconOut);

 

Y los callbacks:

 

    function iconClick(id, layer, filepos)

    {       

    }

   

    function iconOver(obj, id, layer, filepos)

    {       

    }

   

    function iconOut(obj, id, layer, filepos)

    {    

    }

 

obj = objeto IMG

id  = ltnid del icono

layer    = capa a la que pertenece el icono

filepos = identificador filepos del icono

 

Herramientas personalizadas

Es frecuente necesitar herramientas que operen sobre el visor pero que no están diseñadas de fábrica para el requerimiento de cada desarrollo. Para este caso se incluye soporte que permite desde código de servidor, crear botones personalizados que se integran con los botones que trae por defecto el visor Web.

 

/// <summary>

/// Botón para herramienta personalizada

/// </summary>

public class MyButton: OLatino.LtnWebViewer.Tool.LtnWVButtonState

{

    public MyButton(string visorID)

    {

        this.ViewerID = visorID;

        this.Image = "smile.gif";

    }

 

    protected override void RenderButtonInit()

    {

        base.RenderButtonInit();

        OnMouseDown += "alert('ejecución correcta');";

    }

}

 

Creamos un botón de este tipo con el siguiente código de servidor:

 

MyButton cb = new MyButton(LtnWebViewer1.ID); //instancia nuevo botón personalizado

this.Controls.Add(cb); //agrega el botón a la página