Consumiendo mi Primera Api con Leaflet y Javascript

Consumiendo mi Primera Api con Leaflet y Javascript

Esta publicación la escribo gracias a algunos pedidos de personas que les intereso el ejercicio que hice sobre un mapa que muestre los datos de contagiados por coronavirus en el Mundo usando el API de : Wuhan Coronavirus

Para este ejemplo lo primero que debemos tener es nuestro esquema HTML, en el cual cargamos los archivos CSS y Javascript de la Librería Leaflet para usar los mapas en alternativa a Google Maps, además de algunos plugins que robustecen la funcionalidad de Leaflet y por supuesto debemos incluir un elemento HTML, en este caso un DIV donde se mostrará el Mapa.

<!DOCTYPE html>
<html lang="es">
<head>
    <!-- Etiquetas para que en dispositivos móviles se pueda ver correctamente el modo en pantalla completa-->
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
    <title>COVID 19</title>
    <!-- Cargar los Estilos del Mapa-->
    <link rel="stylesheet" href="./assets/leaflet-core/leaflet.css" />
    <!-- Cargar el archivo JS del Mapa-->
    <script src="./assets/leaflet-core/leaflet-src.js"></script> 
<!-- Cargar el archivo CSS y JS del Plugin para Pantalla Completa en el Mapa-->
        <link rel="stylesheet" href="./assets/leaflet-fullscreen/Control.FullScreen.css" />
        <script src="./assets/leaflet-fullscreen/Control.FullScreen.js"></script>
    <!-- Hoja de Estilos Propia -->
    <link rel="stylesheet" href="style.css">
    <!-- Plugin para mejorar la carga de las capas de los Mapas -->
    <script src="./assets/leaflet.edgebuffer.js"></script>
    <!-- Plugin para poder cambiar el estilo de color de la Capa a Usar -->
    <script src="./assets//leaflet-tilelayer-colorfilter.min.js"></script>
</head>
<body>
    <!-- Contenedor donde se va a mostrar el Mapa -->
    <div id="map" class="map"></div>
    <!-- Archivos Javascript Propios -->
    <script type="module" src="main.js"></script>
</body>
</html>

Una vez generado la estructura HTML podemos seguir con el archivo JS para darle funcionalidad a nuestro ejercicio.

Primeramente declaramos las variables a utilizar:

//Para la capa que utilizara el Mapa
let lightLayer = 'https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png';
// Constantes para guardar los iconos para el Modo Oscuro / Claro
const darkIcon = '🌛';
const lightIcon = '☀️';
// Constantes para guardar los valores por defecto para el modo oscuro y modo claro
const filterDark = ['invert:100%'];
const filterLight = [];
// Variable donde guardaremos el filtro Actual
let currentFilter = [];
// Variable donde guardaremos el icono del boton actual
let btnIcon = lightIcon;
// Constante donde guardaremos la atribución a mostrar en el Mapa
const leafletAtribution = '&copy; <a href="https://www.openstreetmap.org/copyright">Gracias a OpenStreetMap</a>';

También necesitamos algunas funciones que nos permitan manejar el LocalStorage ya que guardaremos el tema que escoja el usuario para que la próxima vez que recargue la página, se quede guardado el tema que se haya escogido a la hora de visualizar el mapa

// Función para obtener el tema guardado, en caso que no haya alguno devolverá el tema claro por defecto
const getThemeMode = () =>{
    const mode = localStorage.getItem('sm-mode-theme');
    if(!mode){
        return 'light'
    }else{
        return mode;
    }
}
// Función para guardar el tema escogido en el LocalStorage
const setThemeMode = (mode) => {
    localStorage.setItem('sm-mode-theme', mode);
}
// Función para alternar el Tema cuando se haga click en el boton de alternar tema
const toggleThemeMode = () => {
    const mode = getThemeMode();
    if(mode == 'light'){
        setThemeMode('dark');
    }else{
        setThemeMode('light');
    }
}

Hecho esto realizamos algunas validaciones al cargar la página para ofrecer la mejor experiencia al usuario detectando si las preferencias del usuario en el modo de visualización

// Verificamos el esquema de color de preferencia que tenga configurado el usuario, si es el tema oscuro lo guardamos caso contrario se toma por entendido que la preferencia es el tema claro
if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matchess) {
    localStorage.setItem('sm-mode-theme', 'dark');
}
// Hecho esto verificamos que en el LocalStorage exista un valor de tema por defecto, si no existe guardamos el modo Claro por Defecto
if(localStorage.getItem("sm-mode-theme") == null){
    setThemeMode('light');
}
// Actualizamos el filtro que debemos usar en el Mapa dependiendo del tema guardado en el Local Storage
currentFilter = (getThemeMode() == 'light') ? filterLight: filterDark;

Lo siguiente que debemos hacer es crear el Mapa usando la variable L que exporta la librería Leaflet](leafletjs.com/reference-1.6.0.html#map-option) y nos permita inicializar un Mapa pasándole como parámetros, primeramente el ID del elemento HTML donde se va a mostrar y después un objeto con los parámetros que podemos sobreescribir, las que usaremos en este tutorial son:

  • zoomAnimation: activar/desactivar la animación realizada al hacer Zoom
  • markerZoomAnimation: activar/desactivar la animación del marcador
  • zoomControl: mostrar o ocultar el botón de control de Zoom

Además configuramos las coordenadas por defecto que cargara el mapa, y el nivel de Zoom que tendra en la opción setView

let map = L.map('map', {
    zoomAnimation: false,
    markerZoomAnimation: false,
    zoomControl: true,
}).setView([0, 0], 3);

Luego creamos una capa usando el filtro de la librería Color Filter pasándole como primer parámetro la URL de la Capa y como segundo parámetro las opciones de configuración de la Capa.

  • attribution: Mensaje a Mostrar en el mapa dando los créditos a los autores
  • filter: filtro de Color a utilizar
let mapTileLayer = L.tileLayer.colorFilter(lightLayer, {
    attribution: leafletAtribution,
    updateWhenIdle: true,
    reuseTiles: true,
    filter: currentFilter,
}).addTo(map);

Además añadimos en el mapa un control para el modo de pantalla completa.

let fsControl = L.control.fullscreen();
map.addControl(fsControl);

Hecho esto vamos a crear un control personalizado para agregarlo al mapa. Lo primero que debemos hacer es inicializar un objeto tipo Control y en la propiedad onAdd pasarle una función que retorna un elemento de la clase DomUtil, en este caso de tipo input, al cual le asignamos el tipo como botón y el valor será el icono del botón. Por último retornamos el objeto creado y añadimos ese control al mapa.

let btnThemeControl = L.control();
btnThemeControl.onAdd = function (map) {
    let container = L.DomUtil.create('input');
    container.type = "button";
    container.value = btnIcon;
    container.title = "Modo Visualización";
    //Función que alterna el tema, actualiza la capa y actualiza la clase y el value del botón.
    container.onclick = function (event) {
        toggleThemeMode();
        //Cambiar Boton, Clase y Filtro de Mapa
        if(getThemeMode() == 'light'){
            event.target.value = lightIcon;
            event.target.classList.remove('dark');
            mapTileLayer.updateFilter(filterLight);
        }else{
            event.target.value = darkIcon;
            mapTileLayer.updateFilter(filterDark);
            event.target.classList.add('dark');
        }
    }
    container.classList.add('btnThemeControl')
    return container;
};
btnThemeControl.addTo(map);

También necesitamos una función que haga la petición al API y nos devuelva la respuesta con los datos necesarios para mostrarlos en el Mapa

//Funcion para traer los datos de un API y retornar el JSON de Respuesta
async function getData() {
    const response = await fetch('https://wuhan-coronavirus-api.laeyoung.endpoint.ainize.ai/jhu-edu/latest')
    const data = await response.json()
    return data
}

La siguiente función que usaremos será para generar el HTML que se mostrara en el Popup del Marcador una vez le demos click, en este Popup mostraremos el Pais, el numero de personas contagiadas, fallecidas y recuperadas

//Función renderizar datos
function renderExtraData({ confirmed, deaths, recovered, provincestate, countryregion }) {
    return (`
        <div>
          <p> <strong>${provincestate} - ${countryregion}</strong> </p>
          <p> Confirmados: ${confirmed} </p>
          <p> Muertes: ${deaths} </p>
          <p> Recuperados: ${recovered} </p>
        </div>
      `)
}

Lo siguiente que debemos crear es el Icono personalizado a mostrar en los marcadores que creemos, los recursos los pueden encontrar en el repositorio de Github que lo dejo al final del Post. Leaflet tiene un objeto Icon que cuando lo inicializamos le podemos mandar parametros como la URL del Icono, del Shadow o Sombra del Marcador, el tamaño de la sombra y del icono, entre otros.

const iconUrl = './icon.png';
const shadowIcon = './marker-shadow.png';
const icon = new L.Icon({
    iconUrl: iconUrl,
    shadowUrl: shadowIcon,
    shadowSize: [40, 40],
    iconSize: [40, 40],
    iconAnchor: [12, 41],
    popupAnchor: [1, -34],
});

Finalmente codificamos la función que renderizara los datos en la pantalla, y lo que hace es lo siguiente:

  • Obtener los datos del Api
  • Recorrer la lista de información y por cada dato crear un marcador, asignarles las coordenadas y el icono del marcador, para añadirlos al mapa, sin olvidarnos de setear el contenido del Popup.
async function renderData() {
    const data = await getData();
    data.forEach((item, index) => {
        const marker = L.marker([item.location.lat, item.location.lng], { icon: icon })
        .bindPopup(renderExtraData(item))
        .addTo(map);
    });
}

Lo que debemos hacer al final es llamar a la función para renderizar los datos.

renderData()

Postdata Adjunto los estilos utilizados

body {
  margin: 0;
}
.map {
  width: 100vw;
  height: 100vh;
}

.info {
  padding: 6px 8px;
  font: 14px/16px Arial, Helvetica, sans-serif;
  background: #22303a;
  box-shadow: 0 0 15px rgba(0, 0, 0, 0.2);
  border-radius: 5px;
  padding: 12px 10px;
  text-align: center;
}
.info h4 {
  margin: 0 0 5px;
  font-weight: bold;
  color: white;
}
.info span {
  color: #66d1ff;
}
.btnThemeControl {
  padding: 8px;
  font-size: 24px;
  background: white;
  outline: none !important;
  border: none !important;
  cursor: pointer;
  height: 50px;
  width: 50px;
  border-radius: 50%;
}
.btnThemeControl:focus {
  outline: none !important;
}
.btnThemeControl::-moz-focus-inner {
  border: 0 !important;
}
.btnThemeControl.dark {
  background: #383434;
}
.leaflet-center {
  left: 50%;
  transform: translate(-50%, 0%);
  margin: 1em;
}

Muchas gracias por leer esta publicación, si te gusto por favor compártela con quien lo necesite, puedes revisar el repositorio y observar el resultado final en este enlace