Leer capas WMS con WorldWind

De ChuWiki

Tanto en internet como disponibles para que nos instalemos en nuestro PC hay servidores de mapas que nos ofrecen mapas usando el protocolo WMS ( Web Map Service ). Geoserver es uno de estos servidores, hecho en java y gratuito, que nos podemos descargar e instalar. Tiene algunos mapas instalados como ejemplo (Tasmania y Estados Unidos) y los ofrece, entre otros, por medio del protocolo WMS.

WorldWind es una librería opensource desarrollada por la NASA que nos permite dibujar un globo terráqueo, al estilo google earth, con mapas por defecto o los que nosotros indiquemos, entre otros, mapas ofrecidos por un servidor WMS como Geoserver.

Veamos aquí un ejemplo de cómo dibujar en WorldWind los mapas ofrecidos por Geoserver.

Por supuesto, necesitamos instalar Geoserver al menos con sus mapas por defecto y vamos a suponer que lo tenemos accesible en la URL http://localhost:8080/geoserver


Obtener las capas ofrecidas por el servidor WMS[editar]

Desde nuestro código java en WorldWind, podemos poner las siguientes líneas de código

WMSCapabilities capabilities = WMSCapabilities.retrieve(new URI("http://localhost:8080/geoserver/wms"));
capabilities.parse();

La llamada a WMSCapabilities.retrieve() obtiene todas las capacidades de un servidor WMS sin más que pasarle la URI del servidor. Nuestra uri de Geoserver era http://localhost:8080/geoserver/ y añadiendo detrás wms, obtenemos la URI del servidor WMS de Geoserver http://localhost:8080/geoserver/wms. Esta llamada pide al servidor WMS por sus capacidades (qué mapas sirve, en qué formatos, etc) y la respuesta viene en XML. Nosotros, afortunadamente, no vemos ni tenemos que analizar ese XML, la clase WMSCapabilities se encarga de todo.

Obtenidas las capabilities, es necesario llamar al método parse() para que se analice la respuesta XML del servidor WMS y poder así llamar a los métodos get de capabilities y obtener resultados coherentes.

Crear las capas WorldWind[editar]

WMS puede servir varios mapas y los mapas pueden a su vez tener capas (por ejemplo, una capa de fronteras, otra capa de calles, otra capa de cidudades, otra capa de rios, etc, etc). La forma de saber qué mapas y capas ofrece un servidor WMS es llamar al método capabilities.getNamedLayers()

List<WMSLayerCapabilities> listaCapas = capabilities.getNamedLayers();

Este método devuelve una lista (List<>) con todas las capas. Para cada capa, podemos llamar al método getName() para saber su nombre. También podemos llamar a otros métodos que nos den información legible de la capa, por ejemplo, la siguiente secuencia de llamadas

System.out.println(unaCapa.getName());
System.out.println(unaCapa.getLayerAbstract());
System.out.println(unaCapa.getTitle());

siendo unaCapa uno de los elementos de la listaCapas, puede dar una salida como esta

topp:states
This is some census data on the states.
USA Population

Como en nuestro ejemplo queremos añadir todas las capas, haremos un bucle para añadirlas todas. Si sólo quieres añadir una o unas cuantas, tendrás que saber previamente su nombre (o alguna característica de la capa) que te permita buscar la capa deseada en la lista y dibujarla. Por ejemplo, podrías buscar una capa cuyo getName() sea "topp:states" si quieres dibujar la población de USA.


Vamos con el bucle

for (WMSLayerCapabilities unaCapa : listaCapas) {
   AVListImpl params = new AVListImpl();
  

Para cada capa necesitaremos una pequeña lista con determinados parámetros, como nombre de la capa, timeouts de conexión con el servidor WMS, etc, etc. Esa lista de parámetros es una instancia de la clase AVListImpl, que es la que hemos creado inmediatamente dentro del for. Vamos a ir rellenando cosas

params.setValue(AVKey.LAYER_NAMES, unaCapa.getName());

params.setValue(AVKey.URL_CONNECT_TIMEOUT, 30000);
params.setValue(AVKey.URL_READ_TIMEOUT, 30000);
params.setValue(AVKey.RETRIEVAL_QUEUE_STALE_REQUEST_LIMIT, 60000);

La lista es un estilo Hash, se añaden valores pasando una clave y un valor, por medio del método setValue(clave, valor). Las claves están predefinidas y son constantes declaradas en la clase AVKey. Así, AVKey.LAYER_NAMES es la clave correspondiente al dato que es el nombre de la capa.

Rellenamos, como se ve en el código, el nombre de la capa deseada y tres tiempos de tiemout con el servidor WMS.

Hay otros valores que rellenar, pero hacerlo manualmente puede ser complejo. Por ejemplo, hay que rellenar cosas como latitud y longitud máxima y mínima del mapa (puede ser de todo el mundo o puede ser de una ciudad), niveles de zoom, estilos posibles de dibujo, etc, etc, etc. Toda esta información normalmente la facilita el mismo servidor WMS y se puede obtener interrogando a cada capa (clase WMSLayerCapabilities), pero es un trabajo tedioso ir interrogando y rellenando nuestro AVListImpl. Por ello, vamos mejor a utilizar unas clases que nos ofrece WorldWind y que hacen todo el trabajo.

Factory factory = (Factory) WorldWind.createConfigurationComponent(AVKey.LAYER_FACTORY);
Object laCapa = factory.createFromConfigSource(capabilities, params);

Creamos una fábrica de capas por medio de la llamada WorldWind.createConfigurationComponent(AVKey.LAYER_FACTORY). Podemos usar la instancia devuelta para crear cada una de las capas de WMS. La creación de la capa se hace con la llamada factory.createFromConfigSource() a la que pasamos dos parámetros

  • capabilities son las capacidades que obtuvimos del servidor WMS. El método lo necesita para saber qué ofrece el servidor WMS y en función de eso crear la capa de forma adecuada.
  • params, nuestos parámetros, donde está el nombre de la capa deseada.

El resultado de esta llamada es una capa (laCapa) correctamente configurada. Si miramos el contenido de params, veremos que la llamada ha rellenado también todos esos valores complicados que comentábamos antes, como extensión del mapa, niveles de zoom posibles, etc, etc. Por ejemplo, recorriendo los params después de hacer la llamada a createFromConfigSource() con el siguiente código

Set<Entry<String,Object>> entries = params.getEntries();
for (Entry<String,Object> entry : entries){
   System.out.println(entry);
}

obtenemos todos estos valores, entre los que aparecen los que pusimos manualmente.

gov.nasa.worldwind.avkey.CoordinateSystem=EPSG:4326
gov.nasa.worldwind.avkey.TileURLBuilder=gov.nasa.worldwind.wms.WMSTiledImageLayer$URLBuilder@1ab87723
gov.nasa.worldwind.avkey.URLConnectTimeout=30000
gov.nasa.worldwind.avkey.GetCapabilitiesURL=http://localhost:8080/geoserver/ows?SERVICE=WMS&
gov.nasa.worldwind.avkey.UseTransparentTextures=true
gov.nasa.worldwind.avkey.DatasetNameKey=topp:states
gov.nasa.worldwind.avkey.ServiceURLKey=http://localhost:8080/geoserver/ows?SERVICE=WMS&
gov.nasa.worldwind.avkey.LayerNames=topp:states
gov.nasa.worldwind.avkey.URLReadTimeout=30000
gov.nasa.worldwind.avkey.ServiceName=OGC:WMS
gov.nasa.worldwind.avKey.Sector=(24.955967°, -124.731422°), (49.371735°, -66.969849°)
gov.nasa.worldwind.avkey.TileWidthKey=512
gov.nasa.worldwind.avkey.WMSVersion=1.3.0
gov.nasa.worldwind.avkey.FormatSuffixKey=.dds
gov.nasa.worldwind.avkey.NumEmptyLevels=0
gov.nasa.worldwind.avkey.ImageFormat=image/png
gov.nasa.worldwind.avkey.LevelZeroTileDelta=(Lat 36,0000°, Lon 36,0000°)
gov.nasa.worldwind.avkey.RetrievalStaleRequestLimit=60000
gov.nasa.worldwind.avkey.DisplayName=USA Population
gov.nasa.worldwind.avkey.NumLevels=19
gov.nasa.worldwind.avkey.TileHeightKey=512
gov.nasa.worldwind.avkey.GetMapURL=http://localhost:8080/geoserver/ows?SERVICE=WMS&
gov.nasa.worldwind.avkey.DataCacheNameKey=localhost_8080\_geoserver_ows\topp_states

Ahora sólo nos queda añadir la capa a nuestro mapa WorldWind y actualizar el panel de capas (ver Empezar con WorldWind). El código para hacer esto es

if (laCapa instanceof Layer) {
   ((Layer) laCapa).setEnabled(false);
   ApplicationTemplate.insertBeforeCompass(wwd, (Layer) laCapa);
   panelCapas.update(wwd);
   wwd.redraw();
}

Comprobamos que laCapa es instancia de Layer para poder hacer el cast a Layer con eseguridad, deshabilitamos la capa con setEnabled(false) para que no se dibuje por defecto en el mapa hasta que nosotros lo indiquemos. Añadimos la capa al mapa con insertBeforeCompass(). Actualizamos nuestro panel de capas para que muestre esta nueva capa añadida con panelCapas.update() y finalmente le decimos al mapa que se repinte (no es realmente necesario, puesto que nuestra nueva capa está deshabilitada).

El código completo[editar]

Puedes ver el código completo de este ejemplo en CapaWMS.java y el main en MainMinimo.java

La siguiente imagen muestra el resultado con la capa que hemos mencionado de población USA y que viene de ejemplo en Geoserver