Todo empezó como un ejercicio de curiosidad, pensando si el ataque al puente de Kerch en Crimea se habría visto desde el espacio.
Para mi sorpresa, este será uno de los menos visibles de los ataques ocurridos durante la guerra de Ucrania.
Con cada noticia, comprobaba las imágenes satélite (accesibles públicamente) de la Agencia Espacial Europea y encontré evidencias de muchas de las informaciones. Las imágenes satélite son puramente objetivas. No hay manipulación o un discurso detrás.
Esto es totalmente opuesto a tomar un bando. Se trata de mostrar la realidad y la escala de la guerra. La auténtica y única verdad es que la violencia y la guerra tienen que ser condenadas en todas sus formas y prevenidas con cada mínimo esfuerzo que uno pueda hacer.
#WarTraces empieza en Ucrania, porque está cerca de los europeos, 24/7 en TV, y afectando a muchas y poderosas economías. Pero hay muchos otros conflictos activos en el mundo. Muchos otros que parecen olvidados. Muchos otros que no parecen tener importancia. Muchos otros que deben cesar igual que este.
Sígueme en 🐦 Twitter para saber cuando se actualiza este post!
Los primeros objetivos rusos fueron aeródromos que neutralizarían el combate aéreo. El Aeropuerto Internacional Antonov supuso una dura batalla desde el día 1 El poblado de Verkhnotoretske fue un frente duro durante el primer mes, a escasos 20km de Donetsk Mariupol fue fuertemente bombardeada al inicio del conflicto. Observa la diferencia de la señal infrarroja habitual y la causada por el incendio de las bombas.
El edificio de gobierno de Mikolaiv fue parcialmente destruido y 38 personas murieron el 29 de marzo.
Al sur de Zaporizhzhia se encuentra la central nuclear más grande de Europa. Fue rápidamente atacada y ocupada en marzo. Ataques continuados, como este de agosto, podrían causar una catástrofe nuclear.
La reclamada Crimea está conectada a la Rusia continental por el puente de Kerch. Un camión bomba causó su destrucción parcial en octubre de 2022.
El ataque al puente de Kerch llevó a un cambio de estrategia y la infraestructura crítica para la electricidad y el agua está siendo los nuevos objetivos desde entonces, a la vista de la inminente llegada del invierno.
Suele estar nublado en Ucrania, pero estas nubes no son muy normales. Las estelas que dejan las aeronaves también se ven desde el aire y nos dan información sobre sus rutas y su altitud.
Las calles residenciales de Bakhmut, ciudad clave para la ofensiva en el Donbás, están siendo objeto de duros combates. La mina de sal más grande de Europa, en Soledar, consta de 200km de túneles de 30 metros de alto, útiles para resguardar tropas y equipamiento bélico durante el invierno, a escasos 15km de Bakhmut.
Un mes desde que observé la battal por Bakhmut en un barrio residencial al este.
Infrared activity in Bakhmut doesn't cease being the hardest front of the war so far Desde el 10 de octubre de 2023, los brutales intentos de capturar Adviidka siguen causando muertos.
El 6 de noviembre de 2022, ataques sobre campos de refugiados acabaron en 9 muertes (3 de ellas niños). La gente ha sido desplazada de sus hogares después de destruir los principales asentamientos y ciudades. Puedes ver que no existían estos campos en 2015.
El final de 2017 resultó en intensos bombardeos de ciudades presuntamente ocupadas en el norte y este de Siria.
Tras varios artículos sobre el uso de información satélite vamos a ver cómo unirlo (casi) todo en un ejemplo práctico e impactante. Y es que el tamaño de los últimos incendios ocurridos en España han llamado mucho mi atención y no era capaz de hacerme una idea de lo brutales que han sido (aunque nada que ver con otros sitios como Chile, Australia, o EEUU). Pero hagamos el ejercicio sin gastar demasiados GB de información geográfica.
Lo que quiero es mostrar la extensión del incendio ocurrido en Asturias en marzo, pero quiero también intentar mostrar el impacto retirando los árboles afectados por el fuego. ¡Vamos allá!
Contenido
Descarga de datos
Usaré un Modelo Digital de Superficies (que incluye árboles y estructuras), una ortofoto tomada durante el incendio, y un Modelo Digital del Terreno (al que han eliminado los árboles y las estructuras) para reemplazarlo por las zonas afectadas por el incendio.
1. Modelos del terreno
Usaré los modelos del genial IGN, descargando los productos MDS5 y MDT5 de la zona.
Usaremos el proceso i.group de GRASS en QGIS para agrupar las distintas bandas capturadas por el satélite en un único ráster RGB, tal y como vimos en este post:
Tendremos que hacerlo para cada una de las regiones descargadas, cuatro en mi caso, que luego volveremos a unir usando el proceso Construir ráster virtual.
1. Imagen de color verdadero (TCI)
Combinamos las bandas 4, 3, 2.
2. Imagen de falso color
Combinamos las bandas 5, 4, 3.
3. Ajuste de la tonalidad
Para obtener un mejor resultado, puedes regular los valores mínimos y máximos que se consideran en cada banda que compone la imagen. Estos valores se encuentran en el Histograma de las propiedades de la capa.
Aquí te dejo los valores que yo he usado para obtener el resultado de arriba:
Banda
TCI min
TCI max
FC min
FC max
1 Rojo
-100
1500
-50
4000
2 Verde
0
1500
-100
2000
3 Azul
-10
1200
0
1200
Extensión del incendio
Como ves, la imagen en falso color nos muestra claramente la extensión del incendio. Con ella, vamos a generar un polígono que delimite el alcance del incendio.
Primero consultaremos los valores de la banda 1 (rojo) que ofrece mayor contraste para la zona del incendio. Más o menos están en el rango 300-1300.
Usando el proceso Reclasificar por tabla, asignaremos el valor 1 a las celdas dentro del rango, y el valor 0 al resto.
Vectorizamos el resultado con el proceso Poligonizar y, contrastando con la imagen satélite, seleccionamos aquellos polígonos que correspondan con el incendio.
Usaremos la herramienta Disolver para unir todos los polígonos en un elemento, y Suavizar para redondear ligeramente los contornos.
Ahora obtenemos su inverso. Extraemos la extensión de la capa Landsat y, posteriormente, hacemos la Diferencia con el polígono del incendio.
Procesar terreno
1. Combinar los datos del terreno
Lo primero que haremos es combinar los distintos archivos que conforman los modelos en un archivo único (un único fichero MDS y un único fichero MDT).
Usamos el proceso GDAL - Miscelánea ráster - Construir ráster virtual
2. Extraer datos del terreno
Extraemos los datos que nos interesan de cada modelo:
Del MDS extraemos la superficie afectada por el incendio, de modo que quitaremos los árboles que hayan en él.
Con el MDT hacemos lo inverso: dejamos el terreno (sin árboles) de la zona del incendio, para sustituir los huecos generados en el otro modelo.
Usaremos el proceso Cortar ráster por capa de máscara empleando las capas generadas en el apartado anterior.
Finalmente unimos ambas capas ráster, para que rellenen una a la otra, usando Construir ráster virtual.
Dale vida con Cesium JS
Ya deberíamos tener un modelo de superficies sin árboles en la zona del incendio, pero vamos a intentar verlo de forma interactiva.
Ya mostré un ejemplo parecido, usando un Modelo Digital del Terreno personalizado, así como una imagen satélite reciente, del volcán Tajogaite de La Palma:
En este caso volveré a usar Cesium JS para poder interactuar fácilmente con el mapa (sigue el post anterior para ver cómo subir tus ficheros personalizados al visor Cesium JS).
Para esta ocasión he creado una pantalla dividida (usando dos instancias de CesiumJS) para poder comparar el antes y el después del incendio. Aquí tienes una vista previa:
Espero que te guste! Aquí tienes el código completo y el enlace a github para que puedas descargarlo. Y recuerda, comparte tus dudas o comentarios en twitter!
Hay veces que un Modelo Digital del Terreno (MDT) se queda corto o no está muy limpio. Si tienes acceso a datos LIDAR, puedes generar tú mismo un modelo del terreno y sacarle más partido obteniendo mayor detalle de las zonas que te interesen. Vamos a ver cómo.
Contenido
1. Descarga de datos
Nube de puntos
Voy a usar los magníficos datos públicos del Instituto Geográfico Nacional (IGN, España) obtenidos mediante vuelos que utilizan técnicas de medición láser (LIDAR).
Accede a los datos LIDAR del Centro de Descargas del IGN.
Descarga los archivos PNOA-XXXX...XXXX-RGB.LAZ. RGB emplea color verdadero; ICR, infrarrojo. Pero ambos son válidos.
TIP! Descarga todos los ficheros a la vez utilizando el fichero el applet del IGN. Es un fichero .jnlp que requiere JAVA instalado en Windows o IcedTea en Linux (sudo apt-get install icedtea-netx)
2. Procesado de nube de puntos LIDAR en QGIS
Visualización directa
Desde las últimas versiones (p.e.: 3.28 LTR Firenze), QGIS permite visualizar directamente los ficheros de nube de puntos.
En el menú Capa -> Añadir capa... -> Añadir capa de nube de puntos...
Se mostrarán los datos que hemos descargado en color verdadero, que podemos clasificar en las Propiedades de Simbología, eligiendo la representación de Clasificación por tipo de datos:
Vista 3D
Otra función que trae ahora QGIS por defecto es la visualización de la información en 3D.
Vamos a configurar las propiedades 3D de la capa LIDAR para triangular las superficies y obtener un mejor resultado.
Ahora creamos una nueva vista en el menú Ver -> Vistas del Mapa 3D -> Nueva vista del mapa 3D. Con SHIFT+Arrastrar rotaremos la vista en perspectiva.
Complemento de LAStools
Para utilizar la información fácilmente usaremos las herramientas del plugin LAStools, que instalaremos de la siguiente manera:
TIP! En Linux es recomendable tener instalado Wine para trabajar con los ficheros .exe o será necesario compilar los binarios.
Accede a la web de LAStools y navega a la parte inferior:
La herramienta completa es de pago, pero puedes acceder a la descarga pública para utilizar las funciones básicas que necesitamos.
Descomprime el fichero .zip en una carpeta sencilla (sin espacios ni caracteres especiales)
Ahora abre QGIS, busca el complemento LAStools e instálalo.
Por último, configura la ruta de instalación de LAStools (si es distinta de su valor por defecto C:/ ). La configuración mostrada abajo sirve para Linux con Wine instalado (en mi caso uso PlayOnLinux).
Extraer tipos de datos LIDAR
Con LAStools podemos extraer información de los distintos tipos de datos que componen la nube de puntos. Por ejemplo, vamos a extraer solo los datos clasificados como Suelo (que se corresponden con el valor 2).
Con el proceso las2las_filter podremos crear una nueva nube de puntos filtrada:
Selecciona el fichero a filtrar.
En filter, elige la opción keep_class 2 para conservar solo el tipo de datos 2 (suelo)
Deja lo demás por defecto, e introducir 0 en los campos que requieren un valor value (de lo contrario devolverá un error).
Por último, guarda el fichero con formato .laz en una ubicación conocida para encontrarlo fácilmente.
Al finalizar solo tendrás que cargar el fichero generado para ver la nueva nube de puntos con valores exclusivamente del terreno (edificios y vegetación eliminados).
Conversión de LIDAR a vectorial
Ahora usaremos el proceso las2shp para transformar la nube de puntos a formato vectorial y poder operar normalmente con otras herramientas de GIS:
Elige el fichero de nube de puntos filtrado anteriormente.
Especifica 1 punto por registro para extraer todos los puntos de la nube.
Guarda el fichero con formato .shp en una ubicación conocida para encontrarlo fácilmente.
Y esta será tu nube de puntos filtrada en el formato clásico vectorial.
Como verás, la tabla de atributos no cuenta con ningún campo específico. Yo voy a crear un campo ELEV para guardar las coordenadas Z (o cota) y utilizarlas para generar un Modelo Digital del Terreno a continuación.
3. Creación del Modelo Digital del Terreno
Raster a partir de capa de puntos vectorial
Gracias a la integración de GRASS GIS, disponemos de potentes herramientas de procesado vectorial y ráster. Vamos a usar v.surf.idw para generar una malla regular a partir de la interpolación de los datos de una capa de puntos (en este caso se ponderan los valores obtenidos mediante el inverso de la distancia, pero también hay algoritmos para emplear splines).
Seleccionamos la capa vectorial de puntos.
Elegimos el número de puntos para emplear en la interpolación (en este caso los puntos son bastante densos así que elijo 50). Cuantos más elijas, más suavizado será el resultado, pero perderás el detalle de la densidad de la información.
Dejamos la potencia del inverso de la distancia en 2, para emplear el "inverso de la distancia al cuadrado".
Seleccionamos el campo de datos que usará la interpolación (ELEV).
Definimos el tamaño de celda de la malla. Elijo 2 para poder comparar el resultado con el producto MDT 2 metros del IGN.
4. Resultado
Vamos a quitar zoom para ver cómo ha quedado todo:
Nube de puntos LIDAR en RGBMDT de 2 metros a partir de LIDAR
Y ahora veamos un poco más el detalle.
Aplicamos la misma rampa de color al MDT que hemos generado y al producto descargado del IGN para comparar el resultado obtenido. En general es muy bueno, con algunas diferencias en zonas arboladas, siendo más razonable el resultado de nuestro procesado.
MDT 2m LIDARMDT 2m IGNLIDAR + SatIGN + Sat
¡Y eso es todo! Cualquier duda o comentario lo puedes dejar en Twitter!
Si conoces mis artículos de #WarTraces, verás que utilizo imágenes satélite de la Agencia Espacial Europea. Hoy os cuento cómo descargar y manipular estas imágenes de la misión Sentinel 2, así como de Landsat, ya que el tratamiento es similar para ambos.
Lo haremos con QGIS, para lo cual deberás descargar e instalar este programa con el módulo de GRASS GIS incluido.
Contenido
Registro en plataformas.
Necesitaremos crearnos una cuenta para poder acceder a ambas fuentes de datos, rellenando un formulario y confirmando la cuenta por correo electrónico.
Ahora, dibujaremos el polígono delimitador de la zona de interés. Cada punto del rectángulo se marca haciendo click derecho, y cerramos el polígono con doble click derecho.
A continuación, pulsamos el botón de filtro de la barra de búsqueda y seleccionamos la casilla de la misión Sentinel-2 (también podremos delimitar las fechas de la consulta, pero por defecto se ordenará de más reciente a más antiguo).
Por último, aparecerán los productos disponibles para nuestra consulta, los cuales podremos previsualizar o descargar en un fichero .zip comprimido.
NASA Earthdata Search
Igual que antes, deberemos loguearnos primero en su visor de datos.
Podremos definir también una zona de interés mediante las herramientas de geometría de la barra lateral.
Filtramos los productos, por ejemplo seleccionando "Imagery", y seleccionamos HLS Landsat en el catálogo.
En este caso, deberemos seleccionar las bandas que queramos descargar y que veremos a continuación.
Imagen TCI (true colour o color verdadero)
Simplificando (mucho), los satélites capturan imágenes en distintos rangos de longitudes de onda, denominados bandas.
Además, las imágenes o fotografías digitales que usamos para representar la realidad como las ve el ojo humano suelen estar definidas por tres capas de datos: una para el color rojo (Red), otra para el color verde (Green), y otra para el azul (Blue), definiendo en cada una de estas capas un valor para cada pixel con la cantidad de ese color. La combinación de todos los píxeles es lo que simula una imagen similar a la realidad.
TIP! Haz la prueba y haz mucho zoom a cualquier imagen y verás el mosaico de píxeles que la forma.
Utilizaremos las bandas capturadas por los satélites dentro de la longitud de onda visible para generar una imagen tal y como la veríamos desde el espacio.
Sentinel 2
En el caso de Sentinel 2, podemos mostrar directamente una imagen de color verdadero descargando el producto L1C y empleando la capa _TCI.jp2 ubicada en la ruta /GRANULE/DATA_CODE_NAME/IMG_DATA/
La imagen TCI se verá de la siguiente forma:
Deir ez Zor, Siria, 22/10/2017. TCI. Fuente: Copernicus Sentinel 2.
Para Landsat, deberemos combinar las diferentes bandas capturadas individualmente por el satélite dentro de la longitud de onda visible. Estas son:
RED
B04
GREEN
B03
BLUE
B02
Acudimos a QGIS, y usamos la función i.group del módulo GRASS que combinará las capas seleccionadas en una nueva capa multibanda RGB.
Para facilitar la tarea, yo renombro las capas para ordenarlas de la siguiente manera, asegurándote que el script las lee correctamente:
Quedando así:
En este caso no se parece nada a la imagen anterior o a la realidad, así que ajustamos las propiedades para extraer más color de los datos:
En primer lugar nos aseguramos de leer toda la información de las bandas usando los valores mínimo y máximo reales, en lugar de los estimados.
Solo con esto mejora bastante la imagen, pero le falta algo de brillo y de color.
Ajustamos ahora los parámetros de renderizado de color:
Y ya queda algo mucho más realista:
Deir ez Zor, Siria, 21/10/2017. TCI. Fuente: Landsat.
Capa SWIR (short wave infra-red)
Utilizando las bandas de longitud de onda infra-roja, podemos ver más allá de lo que ve el ojo humano, o incluso a través de ciertos objetos, como es el caso de ciertas nubes o el humo.
Durante los incendios del verano, vi cómo Copernicus utilizaba estas imágenes para delimitar la extensión del fuego, y pensé que servirían para localizar el impacto de las bombas que oculta el humo que desprenden. Y así fue cómo se me ocurrió utilizarlo para #WarTraces.
Sentinel 2
Usamos el procedimiento anterior, combinando para este caso las siguientes bandas:
RED
B12
GREEN
B8A
BLUE
B04
Deir ez Zor, Siria, 22/10/2017. SWIR. Fuente: Copernicus Sentinel 2.
Landsat
Realizamos una nueva combinación, en este caso usando las bandas siguientes:
RED
B07
GREEN
B06
BLUE
B04
Deir ez Zor, Siria, 21/10/2017. SWIR. Fuente: Landsat.
Conclusión
Como nota final, es importante destacar el tamaño de pixel, ya que afectará al detalle de la imagen (resolución):
Sentinel 2: 10 metros.
Landsat: 30 metros.
Por otro lado, Landsat cuenta con imágenes desde 2013, mientras que Sentinel solo dispone de imágenes desde 2015.
Además, puedes previsualizar todas estas capas y combinaciones con visores web como EO Browser de Sentinel Hub.
Bombardeos al Aeropuerto Internacional Antonov en Kyiv, Ucrania, el 26/2/2022, usando EO Browser.
Si te atascas o quieres comentar, te espero en 🐦 Twitter!
Blender es (para mí), el programa de modelado 3D gratuito por excelencia. Tiene una amplísima comunidad, muchísima documentación, tutoriales y, sobre todo, continuas actualizaciones y mejoras.
Uno de sus plugins más útiles es BlenderGIS, con el que podremos volcar datos geográficos, georreferenciados o no, para poder modelar con ellos.
Vamos a ver un ejemplo de uso para un modelo de elevaciones.
Contenido
Instalación
Lo primero que debemos hacer es descargar e instalar Blender desde su fuente oficial (su página web) o desde la tienda de aplicaciones de nuestro sistema operativo:
A continuación ejecutamos Blender y abrimos los ajustes de Add-ons (Edit > Preferences > Add-ons).
Pulsamos en "Install..." y seleccionamos el fichero .zip de BlenderGIS.
Ahora podremos buscarlo y activarlo.
Verás que ahora aparece el menú "GIS" en la barra superior de Blender.
Descargar información geográfica
En este ejemplo yo voy a usar un Modelo Digital del Terreno en formato ASCII (.asc), ya que es uno de los formatos de trabajo de BlenderGIS y también el formato estándar de descarga.
Si la información la descargas en otro formato como .tiff o .xyz, lo podrás convertir usando algún programa de escritorio como QGIS o ArcGIS.
MDT
En mi caso, usaré el MDT200 del IGN español, un modelo con paso de malla (o tamaño de celdas) de 200 metros, y es que quiero representar una zona bastante amplia que incluye la provincia de Álava.
Además, podemos usar una ortofoto como textura del terreno. Para ello me ayudaré de QGIS, y cargaré el servicio WMS del PNOA (también del IGN) para recortar la imagen satélite al gusto.
Cargada la ortofoto, la exportaremos como imagen renderizada para la extensión de nuestro MDT, y con tamaño de celda de 20 metros (la ortofoto admite hasta unos 20cm de tamaño de celda, pero el archivo sería ya excesivamente grande, siendo la imagen de 20 metros de 140MB).
TIP! Una forma de optimizar el detalle es generar "teselas", o una cuadrícula de menor tamaño, pero mayor resolución.
Modelado en Blender
Así estaría todo listo para trabajar en Blender.
Usando el menú "GIS", importamos la capa como malla ASCII. Verás que en seguida aparecerá el modelo en pantalla.
TIP! Este modelo está centrado en el origen de coordenadas, pero se podría georeferenciar estableciendo su CRS en las propiedades de "Georeference".
Ahora añadimos la textura satélite:
Creamos un nuevo material.
Creamos una nueva textura y cargamos la imagen satélite.
Ahora nos movemos a la pestaña de UV Editing:
Selecciona la capa de terreno en la ventana derecha, entra en Edit Mode, y "Seleccionar todos" los polígonos (Ctrl+A). Deberías verlo naranja como abajo y asegúrate de estar en la vista "Supertior" (pulsa el número 7).
Despliega las herramientas "UV" del menú superior y proyecta la capa del terreno con "Project from View (bounds)". Esto hará que se ajuste a la extensión de la imagen.
Elegimos la imagen de la textura para aplicarle la proyección y vemos que se ajustan las celdas del modelo a la imagen (haz un poco de zoom para comprobarlo).
Por último, vamos a la pestaña de Shading y añadimos el elemento "Image Texture" seleccionando la imagen de la textura y conectamos el Vector al UV y el Color al Shader (copiar la imagen).
Ahora, si volvemos a la ventana de Layout, nuestro modelo mostrará la imagen satélite perfectamente ajustada.
Y ya estaría listo, con esto puedes ahora editar y exportar tu modelo, por ejemplo, para imprimirlo en 3D o usarlo en Unity3D.
Si has usado software GIS de escritorio, te sorprenderá saber que muchas de esas herramientas geoespaciales también están disponibles para tus mapas web mediante una librería javascript gratuita y de código abierto.
Hoy te enseño TurfJS. Pulsa el botón 🌤 del mapa incrustado y verás una de sus muchas posibilidades: la interpolación por inverso de la distancia al cuadrado o IDW.
Contenido
Instalación
TurfJS cuenta con una práctica web que nos explica todas las funciones disponibles, así como la configuración inicial:
Puedes crear un mapa base, por ejemplo, con Leaflet. Además, TurfJS utiliza sintaxis JSON, por lo que es altamente compatible con las funciones geoJSON de Leaflet.
TIP! Leaflet ha sido recientemente actualizado a la versión 1.9.1. Este post todavía no está actualizado y utiliza la versión 1.7.1.
markers.forEach(function(m){
var marker=L.marker([m.lat, m.lon]).addTo(map);
marker.bindTooltip("<b>"+m.name+"</b><br>"+m.dato).openTooltip();
});
//ASÍ AÑADÍAMOS UN MARCADOR ÚNICO A LEAFLET
//var marker = L.marker([25.77, -80.13]).addTo(map);
//marker.bindTooltip("<b>Estacion1</b><br>m.dato").openTooltip();
Esto lo hace empleando uno de los métodos más habituales: el inverso de la distancia al cuadrado (IDW) o inverse distance weighting para una potencia de 2.
//vector de puntos vacío
var points=[];
//bucle para rellenar el vector de puntos con los datos
markers.forEach(function(m){
points.push(turf.point([m.lon, m.lat],{name: m.name, dato: m.dato}));
});
//convertir a featureCollection de Turf
points=turf.featureCollection(points);
TIP! Fíjate que Turf recibe las coordenadas en longitud-latitud, en lugar del habitual latitud-longitud!
Definir las opciones de la interpolación, entre las que podemos elegir las siguientes:
gridType: o tipo de malla
points (puntos)
square (cuadrados)
hex (hexágonos)
triangle (triángulos)
property: propiedad (o campo) del objeto de puntos que contiene los valores a interpolar.
units: unidades espaciales usadas en la interpolación
miles: millas
kilometers: kilómetros
radians: radianes
degrees: grados
weight: peso o potencia a la que se eleva la distancia, habitualmente 2, aunque valores más altos darán un resultado más suavizado.
//opciones de interpolación: malla rectangular de la variable 'dato' en kilómetros usando potencia de 2
var options = {gridType: 'square', property: 'dato', units: 'kilometers', weight: 2};
Por último, generar la malla interpolada y añadirla al mapa, siendo el valor numérico el tamaño de las celdas de la malla interpolada (0.5 Km):
//crear malla de Turf
var malla=turf.interpolate(points, 0.5, options);
//añadir a Leaflet
L.geoJSON(malla).addTo(map);
Siendo este el resultado (nada concluyente):
Dar formato a la malla y mostrar valores
Aunque no lo parezca, la interpolación está hecha, aunque no se está mostrando correctamente.
Voy a añadir una escala de color que represente los datos, y también a mostrar los valores al pasar por encima de las celdas. Para ello, modificamos las propiedades del objeto geoJSON de Leaflet:
style: función para modificar el estilo de los objetos, evaluando su valor.
onEachFeature: función que se añade a cada objeto para interactuar con él.
En este post explicaba directamente cómo añadir las estaciones de AEMET con ciertos datos, usando mi plugin leafMET para Leaflet (disponible en github):
Vamos a detenernos ahora en el detalle de cómo funciona la API y cómo acceder a todos los datos disponibles.
Contenido
Acceso a la API
Para usar la API necesitamos una clave de acceso, o API key. Accedemos a la página de AEMET OpenData y hacemos click en el botón "Solicitar" debajo de Obtención de API key.
A continuación se nos pedirá una dirección de correo electrónico para enviarnos dicha clave.
Nos enviarán un primer correo de confirmación, y a continuación recibiremos un segundo correo con la clave de acceso. La copiamos y seguimos.
Documentación y ejemplos
Volvemos a la página de AEMET OpenData y entramos al Acceso Desarrolladores.
Nos interesa la documentación dinámica, que muestra todas las peticiones de datos disponibles y que podemos probar directamente desde allí.
Podemos pulsar sobre cada tema y se mostrará la sintaxis a usar para cada petición de datos.
Vamos a desplegar observación-convencional y, dentro, /api/observacion/convencional/todas.
Veremos un icono de aviso rojo a la derecha, donde podemos introducir la API key y probar la petición.
Ahora pulsamos el botón Try it out! y nos dará:
un ejemplo de petición curl
la URL de la petición
el cuerpo, código y encabezados de la respuesta
Si abrimos la dirección URL que nos ofrece el campo "datos" veremos el listado completo de datos de todas las estaciones. A mi me sale todo este chorizo de JSON:
Cargando...
Los campos más interesantes son los siguientes:
idema: ID o código de la estación.
lat y lon: coordenadas de latitud y longitud.
alt: altitud de la estación.
fint: fecha del intervalo (horario) de los datos.
prec: precipitación registrada en ese periodo.
vv: velocidad del viento en la estación.
ta: temperatura ambiente en la estación
Petición de datos para web mediante javascript
Estructura HTML
Voy a desarrollar un ejemplo incrustado para acceder a los datos y hacer uso de ellos. Simplemente creo un documento .html con un botón de solicitud y una línea de texto y una función de solicitud:
Pulsa el botón para solicitar los datos
<html>
<body>
<button id="solicitud" onclick="solicitar()">Solicitar</button>
<p id="texto">Pulsa el botón para solicitar los datos</p>
</body>
<script>
function solicitar(){
document.getElementById("texto").innerHTML="Todavía no he programado eso..";
}
</script>
</html>
TIP! Copia y pega este código en un documento de texto con extensión .html para abrirlo en tu navegador.
Solicitud HTTP
Realizamos la solicitud de datos con javascript usando el objeto XMLHttpRequest. En w3schools nos ponen un ejemplo muy simple:
Como vimos usando la documentación dinámica, después de hacer la solicitud, recibimos un enlace para acceder a los datos, lo cual es otra solicitud, así que debemos anidar dos solicitudes así:
<script>
function solicitar(){
// Definir texto de carga
document.getElementById("texto").innerHTML="Cargando datos...";
// Definir la API Key
var AK=tUApiKey;
// Definir URLs de solicitud
var URL1="https://opendata.aemet.es/opendata/api/observacion/convencional/todas";
var URL2="";
// Crear objetos XMLHttpRequest
const xhttp1 = new XMLHttpRequest();
const xhttp2 = new XMLHttpRequest();
// Definir función de respuesta a la primera solicitud:
// Queremos acceder a la URL del campo "datos"
xhttp1.onload = function() {
// 1º Pasamos la respuesta a JSON:
URL2=JSON.parse(this.responseText);
// 2º Obtenemos el campo "datos":
URL2=URL2.datos;
// 3º Hacemos la nueva petición:
xhttp2.open("GET", URL2);
xhttp2.send();
}
// Definir función de respuesta a la segunda solicitud:
// Modificaremos la línea de texto de la página con información descargada
xhttp2.onload = function() {
// 1º Pasamos la respuesta a JSON (se trabaja mejor así):
var datos=JSON.parse(this.responseText);
// 2º Obtenemos la longitud del objeto JSON
// Esto equivale a registros horarios individuales por estaciones
var registros=datos.length;
// 3º Obtenemos la fecha del primer registro.
// Le daremos un formato más legible ya que está en ISO
var fechaini=datos[0].fint;
fechaini=new Date(fechaini+"Z");
fechaini=fechaini.toLocaleDateString()+" "+fechaini.toLocaleTimeString();
// 4º Obtenemos la fecha del último registro
// Le damos el mismo formato
var fechafin=datos[(datos.length-1)].fint;
fechafin=new Date(fechaini+"Z");
fechafin=fechafin.toLocaleDateString()+" "+fechafin.toLocaleTimeString();
// 5º Unimos la información en la línea de texto
document.getElementById("texto").innerHTML="Se han descargado <b>"+registros+"</b> registros desde el <b>"+fechaini+"</b> hasta el <b>"+fechafin+"</b>. <a href='"+URL2+"' target='_blank'>Ver todos los datos.</a>";
}
// Enviar la solicitud
xhttp1.open("GET", URL1+"/?api_key="+AK);
xhttp1.send();
}
</script>
Veamos el resultado a continuación:
Pulsa el botón para solicitar los datos
Y ya está listo! De esta forma puedes obtener las coordenadas y los valores de las distintas variables meteorológicas horarias de AEMET para usarlas en una tabla o en un mapa web como hemos visto para Leaflet.
Como siempre, deja tus dudas o comentarios en Twitter! 🐦
Lo habitual es ver y trabajar con estos ficheros en algún programa de SIG como QGIS o ArcGIS, pero ¿cómo lo acercamos al resto de usuarios ?¿No sería mejor verlo directamente en 3D y visitarlo virtualmente?
Aquí es donde me topé con CesiumJS: un visor de mapas en 3D al estilo del famoso [G] Earth, pero de código abierto, gratuito y altamente personalizable para hacer tus propios proyectos.
Demo del visor CesiumJS con la nueva cartografía del volcán Tajogaite con imagen satelital de IDE Canarias. Clicka en el mapa para interactuar con él, o ábrelo en su versión completa.
Contenido
Crear cuenta de Cesium ion
Para utilizar CesiumJS es necesario registrar una cuenta para crear un código de acceso o Access Token.
Además, registrándonos podremos subir nuestros propios ficheros y personalizar las vistas 3D que crearemos. Verás que en mi caso he subido el Modelo Digital de Superficies y la imagen satélite que usa el visor. Dejo aquí los enlaces por si quieres usar los mismos:
TIP! Puedes usar el servicio WMS en tu visor GIS para obtener una ortofoto completa, o descargar la que uso yo para este ejemplo (270 MB con resolución de 1m)
Incorporar CesiumJS a tu web.
Para usar CesiumJS configuramos un fichero .html como ya viene siendo habitual.
En este caso voy a copiar directamente uno de sus ejemplos ya que apenas tiene unas 25 líneas:
Como verás, las partes fundamentales son las siguientes:
enlaces a las librerías de CesiumJS en el <head>
un elemento <div> con id "cesiumContainer" dentro del <body>
el código de acceso dentro de la variable Cesium.Ion.defaultAccessToken. Introduce aquí la tuya.
una variable de visor llamada "viewer"
la configuración inicial del visor, especificando las coordenadas iniciales, y los ángulos de inclinación X y Z.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<!-- Include the CesiumJS JavaScript and CSS files -->
<script src="https://cesium.com/downloads/cesiumjs/releases/1.97/Build/Cesium/Cesium.js"></script>
<link href="https://cesium.com/downloads/cesiumjs/releases/1.97/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
</head>
<body>
<div id="cesiumContainer"></div>
<script>
// Your access token can be found at: https://cesium.com/ion/tokens.
// This is the default access token from your ion account
Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI1MTYwNGM0Yi1iYzlkLTRkMTUtOGQyOS04YjQxNmUwZDQ0YjkiLCJpZCI6MTA0Mjc3LCJpYXQiOjE2NjAxMDk4MzR9.syNWeKPLWA2eMrEh4K9hvkyp1oGdbLMaL0Ozk1kaksk';
// Initialize the Cesium Viewer in the HTML element with the `cesiumContainer` ID.
const viewer = new Cesium.Viewer('cesiumContainer', {
terrainProvider: Cesium.createWorldTerrain()
});
// Add Cesium OSM Buildings, a global 3D buildings layer.
const buildingTileset = viewer.scene.primitives.add(Cesium.createOsmBuildings());
// Fly the camera to San Francisco at the given longitude, latitude, and height.
viewer.camera.flyTo({
destination : Cesium.Cartesian3.fromDegrees(-122.4175, 37.655, 400),
orientation : {
heading : Cesium.Math.toRadians(0.0),
pitch : Cesium.Math.toRadians(-15.0),
}
});
</script>
</div>
</body>
</html>
El resultado ya lo habrás visto en varias ocasiones:
Y su código completo es este:
<html lang="en">
<head>
<meta charset="utf-8">
<script src="https://cesium.com/downloads/cesiumjs/releases/1.96/Build/Cesium/Cesium.js"></script>
<link href="https://cesium.com/downloads/cesiumjs/releases/1.96/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
</head>
<body style="margin:0;width:100vw;height:100vh;">
<div style="height:100%;" id="cesiumContainer"></div>
<script>
// Your access token can be found at: https://cesium.com/ion/tokens.
// Replace `your_access_token` with your Cesium ion access token.
Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiI1MTYwNGM0Yi1iYzlkLTRkMTUtOGQyOS04YjQxNmUwZDQ0YjkiLCJpZCI6MTA0Mjc3LCJpYXQiOjE2NjAxMDk4MzR9.syNWeKPLWA2eMrEh4K9hvkyp1oGdbLMaL0Ozk1kaksk';
// Initialize the Cesium Viewer in the HTML element with the `cesiumContainer` ID.
const viewer = new Cesium.Viewer('cesiumContainer', {
//terrainProvider: Cesium.createWorldTerrain()//terreno original
terrainProvider: new Cesium.CesiumTerrainProvider({//terreno modificado
url: Cesium.IonResource.fromAssetId(1255858),
}),
});
// Esconder widgest inferiores
viewer.timeline.container.style.visibility = "hidden";
viewer.animation.container.style.visibility = "hidden";
// Add Sentinel2 imagery
const layer = viewer.imageryLayers.addImageryProvider(
new Cesium.IonImageryProvider({ assetId: 1256129 })
);
// Add Cesium OSM Buildings, a global 3D buildings layer.
const buildingTileset = viewer.scene.primitives.add(Cesium.createOsmBuildings());
// Fly the camera to San Francisco at the given longitude, latitude, and height.
viewer.camera.flyTo({
destination : Cesium.Cartesian3.fromDegrees(-17.8195, 28.6052, 3000),
orientation : {
heading : Cesium.Math.toRadians(-85.0),
pitch : Cesium.Math.toRadians(-30.0),
}
});
</script>
</div>
</html>
Y esto es todo! Verás que puedes crear infinidad de aplicaciones web inmersivas con terrenos detallados por los usuarios y más precisos que los genéricos.
¿Se te ocurre qué más hacer con CesiumJS? Cuéntalo en Twitter 🐦
ACTUALIZACIÓN 2023! Se elimina el servicio WMS horario y se actualiza la versión diaria a V2. Version WMS actualizada a 1.3.0
Estas semanas está de moda comentar el calentamiento que está sufriendo el Mar Mediterráneo y he encontrado muy pocos mapas que me permita pinchar fácilmente sobre él para saber la temperatura que pretende indicar.
Así que he hecho el mío propio:
Vamos a buscar un WMS con la información en formato ráster,
A replicar su leyenda mediante el uso de lienzos <canvas> html,
Y obtener el valor del pixel seleccionado que corresponda con la leyenda.
Contenido
Los datos
Existen muchas y muy liosas fuentes de información.
Puertos del Estado: Mi primer intento fue acudir a Puertos del Estado, pero lo único bueno que encontré es que usan Leaflet.. Tienen un visor en tiempo real, pero es muy complicado acceder a los datos para hacer algo con ellos (aunque ya veremos cómo ).
Copernicus: "Copernicus es el Programa de Observación de la Tierra de la Unión Europea", como bien dicen en su página web, y aglutinan la mayoría de información geográfica y medioambiental producida por los paises miembros de la UE. En su sección marítima, encontramos datos de Temperatura Superficial del Mar (SST: Sea Surface Temperature) obtenidas por satélite y otros sensores de diferentes organismos europeos.
Global Ocean OSTIA Diurnal Skin Sea Surface Temperature
Después de probar todos, el más completo es el mapa global generado por la MetOffice (la AEMET inglesa).
Según indican, se muestra la media horaria en un mapa sin huecos, usando datos de campo, satelitales y de medidores infra-rojo.
El mapa base
Voy a crear un mapa base como ya hemos visto en otros post.
La descargo en formato geoJSON y, para que sea fácil insertarla, la convierto en una variable llamada "countries" en un nuevo fichero CNTR_RG_01M_2020_4326.js insertándole este encabezado (ten en cuenta que hay que cerrar el objeto JSON con "]}"para que se lea correctamente).
A este enlace le añadimos las siguientes funciones de GeoServer "?service=WMS&request=GetCapabilities" lo cual nos dará la información disponible en dicho servicio WMS, como capas, leyendas, estilos, unidades, etc.
Para saber que opciones debemos introducir, consultamos el fichero de metadatos, en concreto las etiquetas:
<Layer queryable="1"><Name> nos dará el nombre de las capas que introduciremos en la propiedad "layers:"
<Style><Name> nos dará los nombres de los distintos estilos disponibles para la representación del ráster, que podemos especificar en la propiedad "styles:".
<LegenURL> nos dará el enlace a la leyenda que emplea ese estilo.
<Dimension> nos dará unidades que podemos consultar en el servicio WMS. En nuestro caso la unidad es temporal, ya que podemos variar la fecha de los datos representados. Lo voy a dejar comentado, por lo que tomará la última fecha disponible, pero luego lo utilizaremos para personalizar la consulta.
SST raster data
Por último, vamos a añadirle la leyenda al mapa para tener una referencia visual.
La insertamos como imagen en el <body> y añadimos el CSS al <style>:
Como verás, así es muy complicado intentar averiguar la temperatura de un punto concreto.
Replicar la leyenda para consultar los datos
Para poder conocer la temperatura correspondiente a un color concreto, deberíamos conocer la posición de dicho color en la leyenda y calcular su valor proporcional a los valores extremos (310kPa= 36.85ºC y 210kPa=-3.15ºC).
El problema es que por política CORS, no podemos hacer una consulta sobre la imagen, que está fuera del dominio de nuestra aplicación. Por otro lado, si añadimos la imagen a nuestro dominio, esta tendrá resolución determinada y puede restringir la precisión de los colores consultados.
Por ello, para replicar la leyenda añado un elemento de lienzo o <canvas> junto a la leyenda.
Y mediante javascript, ayudándonos del HTML Color Picker, vamos definiendo las distintas paradas de un relleno tipo "gradiente" que reemplazará a nuestra leyenda (la añado como función para poder usarla de forma dinámica).
function grad(){
//Generamos la leyenda en el canvas 'gradientC'
var ctx=document.getElementById("gradientC").getContext('2d');
//Definimos un gradiente lineal
var grd=ctx.createLinearGradient(0,150,0,0);
//Calibrar las paradas del gradiente tomando muestras de la imagen
//Descomentar la imagen que va junto al canvas en el html
//Usar HTML color picker: https://www.w3schools.com/colors/colors_picker.asp
grd.addColorStop(0, "rgb(0, 0, 146)"); //0 -> -3.15
grd.addColorStop(0.09, "rgb(0, 0, 247)"); //1
grd.addColorStop(0.185, "rgb(1, 61, 255)"); //2
grd.addColorStop(0.26, "rgb(0, 146, 254)"); //3
grd.addColorStop(0.3075, "rgb(0, 183, 255)"); //4
grd.addColorStop(0.375, "rgb(3, 251, 252)"); //5
grd.addColorStop(0.5, "rgb(111, 255, 144)"); //6 -> 20.0
grd.addColorStop(0.575, "rgb(191, 255, 62)"); //7
grd.addColorStop(0.64, "rgb(255, 255, 30)"); //8
grd.addColorStop(0.74, "rgb(255, 162, 1)"); //9
grd.addColorStop(0.805, "rgb(255, 83, 0)"); //10
grd.addColorStop(0.90, "rgb(252, 4, 1)"); //11
grd.addColorStop(1, "rgb(144, 0, 0)"); //12 -> 36.85
//añadir el gradiente al canvas
ctx.fillStyle = grd;
ctx.fillRect(0,0,255,255);
}
//ejecutamos la funcion del gradiente al inicio
grad();
SST raster data
Crear selector de pixel y calculador de temperatura
Una vez tenemos una leyenda propia, vamos a crear un elemento que muestre el color del pixel seleccionado, así como la temperatura obtenida.
Para poder obtener el color del pixel seleccionado, éste también lo insertamos en un lienzo en el body:
<canvas id="temp">
<img id="pixel" src="" ALT="CLICK PARA OBTENER TEMPERATURA"></img>
</canvas>
<div id="tempTxt">PINCHA SOBRE EL MAR PARA CONOCER SU TEMPERATURA</div>
Y damos un formato adecuado a los nuevos elementos en el <style>:
onMapClick() para obtener el pixel seleccionado y trasladarlo al recuadro inferior. Esto lo hacemos con una petición GET al servicio WMS con las coordenadas correspondientes al pixel donde hacemos click. Es importante tener en cuenta el sistema de referencia que use el mapa del servicio WMS, que en nuestro caso no es el habitual, sino que es EPSG:3857, para la conversión de unidades.
//Añadimos función al hacer click en el mapa
map.addEventListener('click', onMapClick);
function onMapClick(e) {
//ejecutamos la función grad con cada click
grad();
//Obtenemos las coordenadas del pinto seleccionado
var latlngStr = '(' + e.latlng.lat.toFixed(3) + ', ' + e.latlng.lng.toFixed(3) + ')';
//console.log(latlngStr);
//Definir el CRS para enviar la consulta al WMS
const proj = L.CRS.EPSG3857;
//const proj = L.CRS.EPSG4326;
//Definimos los límites del mapa que pediremos al WMS para que sea aproximadamente de 1 pixel
var BBOX=((proj.project(e.latlng).x)-10)+","+((proj.project(e.latlng).y)-10)+","+((proj.project(e.latlng).x)+10)+","+((proj.project(e.latlng).y)+10);
//console.log(BBOX);
//Restablecemos la imagen en cada click
var tTxt=document.getElementById("tempTxt");
var pix= document.getElementById("pixel");
var ctx=document.getElementById("temp").getContext("2d");
//pix.src="";
ctx.fillStyle="lightgrey";
ctx.fillRect(0,0,300,300);
//Realizamos la petición del pixel seleccionado
var xPix= new XMLHttpRequest();
xPix.onreadystatechange = function(){
if (this.readyState == 4 && this.status == 200) {
pix.src=URL+WMS+BBOX;
pix.onload=function(){
ctx.drawImage(pix,0,0,300,300);
tTxt.innerHTML="INTERPRETANDO LEYENDA...";
//Interpretamos el pixel según la leyenda
leyenda();
}
pix.crossOrigin="anonymous";
}
};
xPix.open("GET", URL+WMS+BBOX);
xPix.send();
tTxt.innerHTML="CARGANDO TEMPERATURA...";
}
leyenda() calcula la temperatura que corresponde a dicho pixel seleccionado según la leyenda que hemos creado anteriormente. Muestra el valor de la temperatura en el recuadro inferior y también se indica en la leyenda con una franja blanca. El algoritmo de cálculo consiste en recorrer la leyenda pixel a pixel (verticalmente) y comparar la diferencia de los valores rgb(x,y,z) de la leyenda con los valores rgb del pixel seleccionado. Iremos guardando el valor que vaya obteniendo una menor diferencia hasta llegar al final, por lo que habrá casos en que la solución no sea 100% exacta. No es la mejor manera pero es rápida (de entender y de ejecutar) y bastante efectiva.
function leyenda(){
var ctx=document.getElementById("temp").getContext("2d");
var tTxt=document.getElementById("tempTxt");
//obtenemos el valor RGB del pixel seleccionado
var RGB=ctx.getImageData(5,5,1,-1).data;
//console.log(ctx.getImageData(10,10,1,-1).data);
var key=document.getElementById("gradientC").getContext("2d");
var max=150;
var min=1000;//la máxima diferencia sólo puede ser de 255x3=765
var dif="";
var val="";
//recorremos el gradiente de la leyenda pixel a pixel para obtener el valor de temperatura
for(var p=1;p<=max;p++){
//obtenemos el valor actual
var temp=key.getImageData(1,p,1,-1).data;
//console.log(temp);
//comparamos con el seleccionado y obtenemos la diferencia total
dif=Math.abs(parseInt(temp[0])-parseInt(RGB[0]))+Math.abs(parseInt(temp[1])-parseInt(RGB[1]))+Math.abs(parseInt(temp[2])-parseInt(RGB[2]));
if(dif<min){
min=dif;
val=p;
//console.log("Obj:"+RGB[0]+","+RGB[1]+","+RGB[2]+"\nTemp:"+temp[0]+","+temp[1]+","+temp[2]+"\nDif:"+dif);
}
}
var T=36.85-(val*40/max);
T=T.toFixed(2);
//pintamos una línea de referencia en la leyenda
key.fillStyle="white";
key.fillRect(0,val,255,1);
//definimos la temperatura en el texto
//si el color da gris, hemos pinchado en la tierra
//console.log("T= "+T);
if(RGB[0]==211&RGB[1]==211&RGB[2]==211){
tTxt.innerHTML="PINCHA SOBRE EL MAR PARA CONOCER SU TEMPERATURA";
}else if(typeof T == "undefined"){
tTxt.innerHTML="¡ERROR!<BR>PRUEBA OTRO PUNTO DEL MAR.";
}else{
tTxt.innerHTML="TEMPERATURA APROXIMADA: <br><br>"+T+" ºC";
}
//console.log(key.getImageData(1,150,1,-1).data);
}
Además, omitimos la imagen original de la leyenda y añadimos nuestros propios rótulos.
Por último, añadimos un controlador de fecha en el body:
y definir los valores máximos y mínimos permitidos
//Obtener última hora de actualizacion del mapa
var timeInp=document.getElementById("timeInp");
var t;
var maxT;
var minT;
var xTime = new XMLHttpRequest();
xTime.onreadystatechange = function(){
if (this.readyState == 4 && this.status == 200) {
//convertimos el XML según https://www.w3schools.com/xml/xml_parser.asp
var xmlDoc=this.responseXML;
t=xmlDoc.children[0].children[1].children[2].children[12].children[1].children[5].attributes[4].value;
//Lo convertimos en un objeto fecha quitando los segundos
t=new Date(t);
t=t.toISOString().substring(0,t.toISOString().length-8);
//Lo pasamos al selector y lo establecemos como máximo
timeInp.max=t;
timeInp.value=t;
maxT=new Date(t);
//también establecemos el minimo
t=xmlDoc.children[0].children[1].children[2].children[12].children[1].children[5].innerHTML.trim();
t=t.substring(0,16);
timeInp.min=t;
minT=new Date(t);
}
};
xTime.open("GET", URL+"?service=WMS&request=GetCapabilities");
xTime.send();
//Selector de fecha para WMS
timeInp.addEventListener("change", function(){
t=new Date(timeInp.value.toString());
t.setHours(12-t.getTimezoneOffset()/60);
t=t.toISOString().substring(0,t.toISOString().length-8);
timeInp.value=t;
t=new Date(timeInp.value);
t.setHours(12-t.getTimezoneOffset()/60);
//si estamos en el rango de datos..
if(t>=minT && t<=maxT){
t=t.toISOString();
//actualziamos el mapa
base.setParams({
time: t,
styles: "boxfill/rainbow",
},0);
}else{//mostramos error
alert("La fecha introducida está fuera de rango.");
}
});
//funcion de reinicio de fecha
function reiniT(){
timeInp.value=maxT.toISOString().substring(0,maxT.toISOString().length-13)+"23:30";
}
Resultado
Con todo esto, nos quedará algo así, que puedes ver a pantalla completa aquí:
Si te ha gustado, tienes dudas, o alguna idea para mejorar este mapa, deja tus inquietudes en Twitter 🐦
La Agencia Estatal de Meteorología española (AEMET) cuenta con una API de datos abiertos con la que podemos acceder a la mayoría de datos que publican en su web.
API de AEMET OpenData
De esta forma, cualquier usuario puede crear apps muy sencillas sólo con la información que necesita y sin tener que acceder a su web.
En mi caso, quiero mostrar de forma intuitiva los datos de las estaciones en un mapa web que pueda ver desde cualquier dispositivo con internet como mi móvil. Aquí un anticipo para que sigas leyendo:
Añadimos los enlaces a las librerías de Leaflet dentro de las etiquetas <head>:
También añado las propiedades <meta> charset(1) y viewport(2) para (1)interpretar correctamente los caracteres especiales como tildes y, (2) ajustar correctamente la ventana a dispositivos móviles.
Como verás, añado border:0 y margin:0 al estilo del <body>. Esto ajusta el mapa perfectamente a la ventana, sin ningún tipo de espacio en blanco.
Height:100vh y width:100vw ajustarán el mapa al 100% del tamaño de la ventana (view height y view width). El z-index nos servirá para ordenar los objetos y que no se tapen entre ellos.
Ya podemos generar el mapa mediante javascript. Pasamos al bloque <script>
Con L.map definimos el mapa y su vista inicial (que he centrado en Madrid).
Con L.tileLayer.wms añadimos el servicio WMS del IGN para obtener un mapa base "online". Puedes encontrar las URL completas de cada servicio consultando los metadatos que acompañan a cada producto.
Además, es habitual que los WMS cuenten con varias capas, por lo que debemos definir una. Podemos obtener el listado de capas usando la función GetCapabilities de los wms. En nuestro caso usamos la <Layer> con <Name> "IGNBaseTodo-gris" que identificamos aquí:
Ahora añadimos el plugin de descarga y representación de datos de AEMET leafMET que encontrarás en mi github.
Podemos descargar el fichero leafMET.js y añadirlo como script local, o podemos enlazarlo directamente desde esta web. Dentro de las etiquetas <head> añadimos lo siguiente:
Ahora, para ver la aplicación con un sólo click desde el móvil, debemos copiar el contenido de leafMET.js dentro de las etiquetas <script> del documento .html
Así, tendremos un único archivo con todo lo necesario para correr nuestra webapp. He añadido las etiquetas <title> y "icon" en <head> para mostrar un título de página y que el acceso directo herede el icono que definamos.