Cómo crear mapas web animados con Leaflet

Generalmente los mapas son una representación geográfica de una variable en un momento concreto. Sin embargo con el desarrollo de las nuevas tecnologías podemos construir mapas web animados que nos muestren la evolución de un factor a lo largo del tiempo.

Posiblemente el caso más conocido es el de las aplicaciones de meteorología, donde podemos ver el avance de un huracán o de un frente de lluvia mediante las imágenes de radar.

Una de las librerías JavaScript más utilizadas para la publicación web de mapas es Leaflet. Pues bien, Leaflet dispone de una herramienta que nos permite incluir la dimensión temporal a un mapa. Se trata del plugin Leaflet TimeDimension.

¿Por dónde empezar?

Leaflet TimeDimensión dispone de múltiples opciones y algunas particularidades que a primera vista lo hacen parecer más complejo que otros plugins. Sin embargo despojándolo de todos los elementos no sustanciales y reduciéndolo a lo básico, veremos que es sencillo de aplicar. Además el plugin nos proporciona una excelente documentación con varios ejemplos.

Los datos

Necesitaremos datos que además de la componente espacial tengan una componente temporal. Esos datos pueden ser principalmente de dos tipos:

– Una capa WMS que disponga de un parámetro temporal.

– Una combinación de capas geográficas en formato geoJSON que dispongan de un atributo con fecha/hora que permita realizar una distribución temporal.

En el ejemplo que vamos a describir utilizaremos la segunda opción, es decir utilizaremos datos GeoJson a los que añadiremos un componente temporal en los atributos.

Requisitos

Para poder utilizar este plugin necesitamos cumplir varias condiciones, que detallamos a continuación:

Primera condición: Como en todos los plugins de Leaflet, lo primero que necesitamos son los archivos del plugin, que se compone de un archivo css:

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet-timedimension@1.1.0/dist/leaflet.timedimension.control.min.css" />

y de un archivo JavaScript:

<script src="https://cdn.jsdelivr.net/npm/leaflet-timedimension@1.1.0/dist/leaflet.timedimension.min.js"></script>

Segunda condición: jQuery

<script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.2.1/jquery.min.js"></script>

Tercera condición: Para que no se generen conflictos con los datos temporales el plugin Leaflet timeDimension necesita utilizar un formato normalizado según la norma ISO 8601 que es una norma internacional que abarca el intercambio de datos relacionados con la fecha y el tiempo. Hay una librería javascrip que nos proporciona esta normalización: iso8601-js-period

<script src="https://cdn.jsdelivr.net/npm/iso8601-js-period@0.2.1/iso8601.min.js"></script>

Una vez que hemos satisfecho los requisitos que requiere el plugin, podemos empezar a construir nuestra aplicación.

El plugin Leaflet timeDimension

Este plugin nos permite incorporar nuevas opciones a la clase L.map de Leaflet. Estas nuevas opciones son:

  • timeDimension, que por defecto toma el valor: false. Nos crea automáticamente un nuevo objeto TimeDimension vinculado al mapa, para eso le asignamos el valor true.
  • timeDimensionOptions . Son las opciones para el objeto TimeDimension.
  • timeDimensionControl, que por defecto toma el valor: false. Si lo cambiamos a true nos añade automáticamente un control TimeDimension al mapa.
  • timeDimensionControlOptions : Opciones para el objeto TimeDimension Control.

Resumiendo podemos decir que, con este plugin estamos creando un nuevo objeto timedimension y su control. También nos proporciona una batería de opciones para la configuración de ambos.

L.TimeDimension

El objeto TimeDimension gestiona el componente temporal de una capa. Dispone de varias opciones que se pueden configurar al crear el mapa mediante la opción timeDimensionOptions. Algunas de estas opciones son:

  • times. Por defecto toma el valor: null. Puede ser:

a) Una matriz de tiempos (en milisegundos).

b) Cadena de fechas separadas por comas.

c) Cadena formada por fecha de inicio / fecha de finalización / período.

Si el valor de time es nulo, se construirá de acuerdo con timeInterval y period.

  • timeInterval . Por defecto toma el valor «P1M /» + today. Se emplea para definir el intervalo de tiempo de la variable temporal, en el formato: ISO8601.
  • period. Por defecto «P1D». Se utiliza para construir la matriz de tiempos disponibles a partir del primer tiempo disponible. Formato: ISO8601 Duracion
  • currentTime. Tiempo disponible más cercano o tiempo actual a cargar. Tiempo en ms.

L.Control.TimeDimension

Es el control de Leaflet para gestionar un timeDimension. Nos permite insertar los controles de reproducción | pausa, siguiente, atrás, hora actual, control deslizante de tiempo y control deslizante de velocidad. La lista de opciones que nos proporciona es bastante amplia, y puede consultarse en la documentación del plugin. Aquí vamos a ilustrar el tema con un ejemplo y explicar a continuación las opciones que hemos utilizado.

var map = L.map('map', {
        zoom: 10,
        center: [42.77, -7.90],
        timeDimension: true,
        timeDimensionOptions: {
            timeInterval: "2023-07-20T00:00:00Z/2023-07-27T00:00:00Z",
            period: "P1D"
        },
        timeDimensionControl: true,
        timeDimensionControlOptions: {
            autoPlay: true,
            loopButton: true,
            timeSliderDragUpdate: true,
            speedSlider: true,
            limitSliders: true
        }
    });

 

En el código precedente hemos creado una instancia a la clase L.map de Leaflet y definido unas opciones tales como zoom, o center, de la forma habitual. La novedad de este plugin es que nos permite incorporar nuevas opciones. Al asignar el valor true a timeDimesion y a timeDimensionControl, nos crea un nuevo objeto y un nuevo control. Las opciones que utilizamos en timeDimensionControl son las que nos dan el formato del tiempo, según explicamos antes. El periodo «P1D» indica que el intervalo de tiempo que se utiliza es de un día, esto significa que el control de tiempo del mapa avanzará en incrementos de un día. Las opciones de timeDimensionControlOptions que hemos utilizado las explicamos a continuación:

  • autoplay: anima el mapa automáticamente.
  • loopButton: muestra el botón activar / parar .
  • timeSliderDragUpdate: Esta opción permite que el deslizador de tiempo actualice la visualización de los datos en tiempo real mientras el usuario lo arrastra.
  • speedSlider: Esta opción agrega un control deslizante que permite al usuario ajustar la velocidad de la animación temporal.
  • limitSliders: Esta opción agrega deslizadores adicionales para establecer límites en el intervalo de tiempo que se desea visualizar.

Añadiendo capas con dimensión temporal

El trabajo que hemos realizado hasta el momento tenia por objeto crear las condiciones para poder alojar una capa que tenga un dato de tiempo. Como dijimos al principio estas capa puede ser un WMS con dimensión temporal o unas geometrías geoJSON con un atributo de fecha y/o hora, que es nuestro caso.

// Añadir una capa base (OpenStreetMap)
L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
    attribution: '© OpenStreetMap contributors'
}).addTo(map);

var geojsonFeature = {
    "type": "FeatureCollection",
    "features": [
        {
            "type": "Feature",
            "geometry": {
                "type": "LineString",
                "coordinates": [
                    [-7.8699, 42.7335],  // Sarria
                    [-7.8638, 42.7759],  // Portomarín
                    [-7.8610, 42.8726],  // Palas de Rei
                    [-7.8682, 42.9356],  // Arzúa
                    [-8.0757, 42.9786],  // Pedrouzo
                    [-8.3951, 42.8770],  // Monte do Gozo
                    [-8.5448, 42.8782]   // Santiago de Compostela
                ]
            },
            "properties": {
                "times": [
                    "2023-07-20T00:00:00Z",  // Sarria
                    "2023-07-21T00:00:00Z",  // Portomarín
                    "2023-07-22T00:00:00Z",  // Palas de Rei
                    "2023-07-23T00:00:00Z",  // Arzúa
                    "2023-07-24T00:00:00Z",  // Pedrouzo
                    "2023-07-25T00:00:00Z",  // Monte do Gozo
                    "2023-07-26T00:00:00Z",  // Santiago de Compostela
                ]
            }
        }
    ]
};

var timedLayer = L.timeDimension.layer.geoJson(L.geoJson(geojsonFeature, {
    style: function (feature) {
        return {
            color: 'red',
            weight: 4  
        };
    },
    pointToLayer: function (feature, latlng) {
        return L.marker(latlng);
    }
    }), {
    updateTimeDimension: true,
    updateTimeDimensionMode: 'replace',
    addlastPoint: false
});

// Añadir la capa de datos al mapa
timedLayer.addTo(map);

 

L.TimeDimension.Layer.geoJson()

Implementa una capa TimeDimension para una capa geoJson dada. Las opciones que hemos utilizado en este ejemplo son:

  • updateTimeDimension: Indica que la capa debe actualizarse en función de la dimensión temporal.
  • updateTimeDimensionMode: El valor ‘replace’, especifica que los datos deben reemplazarse en cada actualización temporal.
  • addlastPoint:  El valor ‘false’ indica que no se debe agregar el último punto de la secuencia temporal de forma continua.

Código completo

Vamos a reunir todo el código escrito hasta el momento para facilitar así su entendimiento.

<!DOCTYPE html>
<html>
<head>
    <title>Leaflet TimeDimension Example</title>
    <meta charset="utf-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css" />
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/leaflet-timedimension@1.1.0/dist/leaflet.timedimension.control.min.css" />
    <style>
        #map {
            height: 600px;
        }
    </style>
</head>
<body>
    <div id="map"></div>
    <script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"></script>
    <script src="https://code.jquery.com/jquery-3.7.1.min.js" integrity="sha256-/JqT3SQfawRcv/BIHPThkBvs0OEvtFFmqPF/lYI/Cxo=" crossorigin="anonymous"></script>
    <script src="https://cdn.jsdelivr.net/npm/iso8601-js-period@0.2.1/iso8601.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/leaflet-timedimension@1.1.0/dist/leaflet.timedimension.min.js"></script>
    <script>
        // Crear el mapa
        var map = L.map('map', {
            zoom: 10,
            center: [42.77, -7.90],
            timeDimension: true,
            timeDimensionOptions: {
                timeInterval: "2023-07-20T00:00:00Z/2023-07-27T00:00:00Z",
                period: "P1D"
            },
            timeDimensionControl: true,
            timeDimensionControlOptions: {
                autoPlay: true,
                loopButton: true,
                timeSliderDragUpdate: true,
                speedSlider: true,
                limitSliders: true
            }
        });

        // Añadir una capa base (OpenStreetMap)
        L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
            attribution: '© OpenStreetMap contributors'
        }).addTo(map);

        var geojsonFeature = {
            "type": "FeatureCollection",
            "features": [
                {
                    "type": "Feature",
                    "geometry": {
                        "type": "LineString",
                        "coordinates": [
                            [-7.8699, 42.7335],  // Sarria
                            [-7.8638, 42.7759],  // Portomarín
                            [-7.8610, 42.8726],  // Palas de Rei
                            [-7.8682, 42.9356],  // Arzúa
                            [-8.0757, 42.9786],  // Pedrouzo
                            [-8.3951, 42.8770],  // Monte do Gozo
                            [-8.5448, 42.8782]   // Santiago de Compostela
                        ]
                    },
                    "properties": {
                        "times": [
                            "2023-07-20T00:00:00Z",  // Sarria
                            "2023-07-21T00:00:00Z",  // Portomarín
                            "2023-07-22T00:00:00Z",  // Palas de Rei
                            "2023-07-23T00:00:00Z",  // Arzúa
                            "2023-07-24T00:00:00Z",  // Pedrouzo
                            "2023-07-25T00:00:00Z",  // Monte do Gozo
                            "2023-07-26T00:00:00Z",  // Santiago de Compostela
                        ]
                    }
                }
            ]
        };

        var timedLayer = L.timeDimension.layer.geoJson(L.geoJson(geojsonFeature, {
            style: function (feature) {
                return {
                    color: 'red',
                    weight: 4  // Doble del grosor predeterminado (2)
                };
            },
            pointToLayer: function (feature, latlng) {
                return L.marker(latlng);
            }
            }), {
            updateTimeDimension: true,
            updateTimeDimensionMode: 'replace',
            addlastPoint: false
        });

        // Añadir la capa de datos al mapa
        timedLayer.addTo(map);

        // Inicializar la animación en el punto de inicio
        var timeDimension = map.timeDimension;
        timeDimension.setCurrentTime(new Date("2023-07-20T00:00:00Z").getTime());

    </script>
</body>
</html>

 

El resultado lo puedes ver a continuación.

Si quieres aprender a crear visores web profesionales como éste, inscríbete ya en nuestro curso online de web mapping interactivo con Leaflet.

6 comentarios en «Cómo crear mapas web animados con Leaflet»

  1. Muy buen aporte, tenia una duda talvez basica pero me gustaria porfavor me la puedan aclarar como creo o genero un WMS con dimensión temporal, o en otro caso un geojson que particularidad debe tener porfavor muchas gracias desde ya!!!!!!!

    • Hola Favio:
      Según la especificación GeoJSON, las coordenadas geométricas solo pueden tener tres dimensiones: latitud, longitud y elevación. No hay por tanto una forma estándar de agregar información de dimensión temporal. Este plugin buscará algunos atributos dentro de las propiedades. Lo mejor, sin duda, es que consultes la información del plugin, que es muy completa.
      Un saludo.
      José Luis

  2. Hola José Luis,
    gracias por tu artículo! Trabajo en ICTS SOCIB, el observatorio oceanográfico que ha desarrollado y mantiene este plugin de Leaflet. Es una herramienta que necesitábamos para realizar las visualizaciones de algunos de los datos que tenemos (modelos oceanográficos, corrientes observadas por RADAR HF, trayectorias de boyas de deriva y otros). Y al compartirla en github, hemos conseguido que otras personas la puedan utilizar, y además, algunas de ellas nos ayudan en las mejoras.

    Una corrección. Lo más adecuado sería incluir un sólo fichero js: dist/leaflet.timedimension.min.js. Este fichero es el resultado de concatenar, eliminar el logging y minimizar los 6 ficheros fuente del plugin. En los ejemplos están puesto los 6 ficheros de src para poder explorar mejor el plugin.

    Ah! Y se puede utilizar esta imagen como logo del plugin:
    https://github.com/socib/Leaflet.TimeDimension/blob/master/examples/img/logo-leaflettimedimension.png

    Un saludo,
    Biel

  3. Hola, te escribo desde argentina, mira lo que necesito saber es si se puede hacer que agregando mis clientes en google maps con My Maps, pueda agregar el mapa a mi sitio web con todos ellos y que tengo un buscador de búsqueda para que la persona que quiere buscar restaurantes, les aparezcan los restaurantes que yo cargue marcados en el mapa. Digamos que es como cuando pongo restaurantes en google maps y me aparecen los que tengo cerca. Muchas gracias saludos

    • Hola Alberto, te contesto yo, aunque el autor del post es José Luis García. Referente a tu pregunta me temo que con My Maps no se puede realizar, ya que su funcionalidad es limitada. Debes acudir a la API JavaScript para personalizar la funcionalidad mediante código. Un saludo!

Los comentarios están cerrados.