Constantino
Sánchez Ballesteros
Dpto.
Informática de Sgrin, S.A. – Grupo CADIC
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:
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.
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.
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.
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.
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...
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.
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
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()
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.
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.
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);
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.
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
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
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