Leaflet-Controles Personalizados-Leyenda

Pasos a seguir para crear un control personalizado con información HTML con la leyenda de datos incrustados en el mapa

Anartz Mugika Ledo🤗
10 min readOct 19, 2022

En este artículo os voy a mostrar como crear un control personalizado con una leyenda asociada a los elementos que vamos a dibujar en nuestro mapa.

Si tenéis curiosidad por los mapas y concretamente trabajar con Leaflet os dejo más artículos relacionados (que irá ampliando):

Requisitos mínimos necesarios

Para empezar con ello, lo primero que necesitaremos es cumplir estos requisitos:

  • Tener la versión estable de Node 14 ó más.
  • Tener conocimientos sobre trabajar con Mapas en Leaflet. Si no tenéis ninguna noción, os invito a que aprendáis en este curso gratuito completo y paso a paso! (más de 9 horas de contenido aprendiendo desde un nivel hasta un nivel muy bueno, para trabajar con conceptos bastante avanzados).
  • Descargar el repositorio para empezar a trabajar con ello. Al descargar npm install ó yarn

Una vez que ya cumplimos estos requisitos sencillos, vamos a por el desarrollo del control personalizado con una leyenda.

Antes de nada, si lo deseáis, podéis iniciar el proyecto para tenerlo en marcha e ir haciendo los cambios y así poder ver el resultado paso a paso.

Para iniciar el proyecto, debemos de ejecutar:

npm start

Una vez ejecutado, se debe de abrir una nueva pestaña en nuestro navegador con esta apariencia:

Imagen del proyecto con los ajustes básicos

La ubicación es añadida ya por defecto, pero si queréis practicar con alguna de vuestra zona, deberéis de ir a la línea donde se inicia el objeto del mapa instanciando la clase “Map” del fichero index.ts que se encuentra de src/main y cambiamos las coordenadas.

import { Map } from 'leaflet';
import { startMapTemplate } from '../assets/template/content';
import { tileLayerSelect } from '../config/functions';
startMapTemplate(document, 'Plantilla - Mapa con Typescript');const mymap = new Map('map').setView([43.3082977, -1.9837398], 10);
// Sustituimos 43... (latitud) y -1.88... (longitud)
tileLayerSelect().addTo(mymap);

Aclarado esto, para completar el objetivo, iremos paso a paso y lo primero que vamos a hacer es crear la base, es decir, donde se van a mostrar los elementos que compondrán la leyenda. El objetivo será lo siguiente:

Resultado que conseguiremos al final del artículo

Creamos un nuevo fichero dentro del directorio main

Para crear el elemento de control base, lo primero que vamos a hacer es crear el fichero en src/main llamado legend-control.ts para implementar lo necesario para el control con leyenda y añadimos el siguiente código:

import { DomUtil } from 'leaflet';
import { Util } from 'leaflet';
import { ControlPosition } from 'leaflet';
import { Control } from 'leaflet';
const LegendData = Control.extend({
// Inicialización
initialize: function(options: {
position: ControlPosition, title: string
}) {
Util.setOptions(this, options);
},
// Opciones con sus valores por defecto
options: {
position: 'bottomleft', // bottomright, topright, topleft
title: 'Earthquakes'
},
// Añadir la información para formar el control personalizado
onAdd: function() {
const controlDiv = DomUtil.create('div', 'info legend');
controlDiv.innerHTML = `<h5>${this.options.title}</h5><span>Content legend data</span>`;
controlDiv.style.backgroundColor = 'white';
controlDiv.style.textAlign = 'center';
controlDiv.style.padding = '3px';
controlDiv.style.borderRadius = '6px';
controlDiv.style.border = '1px solid green';
controlDiv.style.marginBottom = '5px';
controlDiv.style.width = '100%';
return controlDiv;
}
});
export const legendData = (options?: {
position?: ControlPosition, title?: string
}) => new LegendData(options);

Para añadirlo en el mapa, solo tenemos que hacer un pequeño cambio en el fichero index con el que se carga el mapa. Hay que “incrustar” ese control como se hace con los tilelayers.

Importamos la constante “legendData” y la implementamos con los ajustes básicos:

  • bottomleft: Control ubicado abajo a la izquierda.
  • title: ‘EarthQuakes’

El código que se implementa es el que corresponde a la línea (2) y lo que se importa se encuentra en la línea (1).

import { Map } from 'leaflet';
import { startMapTemplate } from '../assets/template/content';
import { tileLayerSelect } from '../config/functions';
import { legendData } from './legend-control'; // (1)
startMapTemplate(document, 'Plantilla - Mapa con Typescript');const mymap = new Map('map').setView([43.3082977, -1.9837398], 10);tileLayerSelect().addTo(mymap);legendData().addTo(mymap); // (2)

Una vez implementado la opción de añadir el control en el mapa, se ve así:

Resultado después de dar el primer paso con el control personalizado

Podemos añadir con otro título y abajo a la izquierda por defecto

legendData({
title: 'Terremotos en Gipuzkoa'
}).addTo(mymap);

El resultado se refleja así:

Especificando un título personalizado

Si queremos mover el control a otras esquinas solo se debe de añadir en el valor position las diferentes variantes (topleft, topright, bottomleft, bottomright)

Añadir los marcadores de los terremotos en Gipuzkoa.

Rara vez hay temblores en esta zona, pero como el tutorial es para explicar como crear una leyenda, esto es secundario.

Os propongo este conjunto de datos con ubicaciones de Gipuzkoa con magnitudes generadas aleatoriamente.Descargar el fichero desde aquí: https://gist.github.com/mugan86/c21ab481716b8e86ab2685e051a43263

Cuya estructura será la siguiente:

  • name: Nombre de la localidad
  • location: Coordenadas Geográficas.
  • magnitude: Intensidad del terremoto entre 3 y 10 en la escala de Richter.
  • elevation: Altitud de la localización

Un ejemplo de un elemento sería algo como esto:

{
name: 'Soraluze-Placencia de las Armas',
location: { lat: 43.1742777, lng: -2.4128759 },
elevation: '113',
magnitude: 9,
}

Lo añadimos donde deseemos, yo por no complicarme lo añado en el directorio principal (src/main) como fichero independiente y añado la referencia del import para poder utilizarlo.

import { Map } from 'leaflet';
import { startMapTemplate } from '../assets/template/content';
import { tileLayerSelect } from '../config/functions';
import { legendData } from './legend-control';
import { earthQuakesGipuzkoa } from './earthqueakes-gipuzkoa'; <==
...

También podéis usarlos o practicar con 4–5 puntos que podáis añadir por vuestra cuenta aunque con estos datos ya tenéis suficiente información para practicar e interiorizar los conceptos que se quieren mostrar.

Ahora que ya tenemos los datos necesarios. En el siguiente paso vamos a pintar esos datos en el mapa con círculos, que nos dará la opción de hacerlos más grandes o pequeños dependiendo de su intensidad.

Pintar círculos en el mapa

Volvemos al index.ts y recorremos los puntos donde se han producido los temblores y los añadimos al mapa principal lo que corresponde a la línea dentro de la iteración con las ubicaciones:

import { circle, Map } from 'leaflet';
import { startMapTemplate } from '../assets/template/content';
import { tileLayerSelect } from '../config/functions';
import { legendData } from './legend-control';
import { earthQuakesGipuzkoa } from './earthqueakes-gipuzkoa';
startMapTemplate(document, 'Plantilla - Mapa con Typescript');const mymap = new Map('map').setView([43.3082977, -1.9837398], 10);tileLayerSelect().addTo(mymap);// Añadimos el siguiente código para iterar las ubicaciones y añadir al mapa
earthQuakesGipuzkoa.forEach((point) =>
circle([point.location.lat, point.location.lng]).addTo(mymap);
);
legendData({
title: 'Terremotos en Gipuzkoa',
}).addTo(mymap);

Una vez realizado este cambio, se pintan todas las ubicaciones con los ajustes por defecto y se vería de la siguiente manera:

Primera implementación con los círculos

Como se puede apreciar, ya se han pintado los puntos aunque no nos da mucha información de que magnitud ha sido el temblor ni nada, ya que todos están pintados de la misma manera. Esto lo tenemos que ir ajustando hasta llegar al punto que a simple vista el usuario pueda diferenciar donde hay más gravedad y no teniendo en cuenta los colores y la expansión del círculo, que será mayor si el valor de la magnitud es superior.

Para ajustar el tamaño del círculo, tenemos que tener en cuenta el apartado correspondiente a los círculos en la documentación oficial de Leaflet: https://leafletjs.com/reference.html#circle-radius

El valor que tenemos que tener en cuenta para modificar el radio del círculo es “radius” y este valor se establecerá en metros.

Por el momento, vamos a poner que genere aleatoriamente el radio entre 200–1200 metros con un random.

Añadimos este cambio en el apartado dodne realizamos la opción de añadir círculos, especificando el radio de dicho elemento con un generador aleatorio entre 200–1200 metros.

earthQuakesGipuzkoa.forEach((point) =>
circle([point.location.lat, point.location.lng], {
// 1200 m máximo incluido y 200 el mínimo incluido
radius: Math.floor(Math.random() * (1200 - 200 + 1)) + 200
}).addTo(mymap)
);

Ahora con esto ya conseguiremos un resultado parecido al siguiente, donde se puede apreciar que ya los círculos son de diferentes radios.

Añadir ventana emergente con + información

Ahora que ya hemos añadido los círculos, vamos a añadirle a cada elemento de manera individual su ventana emergente para mostrar la información detallada para cuando realizamos click.

En el apartado donde añadíamos los círculos, lo que tenemos que hacer es únicamente llamar a la función “bindPopup” complementando a lo anterior:

earthQuakesGipuzkoa.forEach((point) =>
circle([point.location.lat, point.location.lng], {
// 1200 m máximo incluido y 200 el mínimo incluido
radius: Math.floor(Math.random() * (1200 - 200 + 1)) + 200
}).bindPopup(`
<h3>${point.name}</h3>
<hr>
<h2>${point.magnitude}</h2>
`).addTo(mymap)
);

El resultado será el siguiente donde se ve el nombre de la localidad y la magnitud del temblor (esto es personalizable, con HTML como lo deseéis)

Colorear los círculos

Ya estamos llegando al punto donde ya empezaremos a dar la forma a la leyenda de nuestro mapa para dar feedback a los usuarios con la magnitud de los temblores de los terremotos.

Antes de nada, vamos a añadir la siguiente función con los colores que se usarán en la escala de Richter:

export function getColor(numberValue: number) {
return numberValue >= 0 && numberValue < 1
? "white"
: numberValue >= 1 && numberValue < 2
? "green"
: numberValue >= 2 && numberValue < 3
? "#6e8c51"
: numberValue >= 3 && numberValue < 4
? "yellow"
: numberValue >= 4 && numberValue < 5
? "#f5d142"
: numberValue >= 5 && numberValue < 6
? "orange"
: numberValue >= 6 && numberValue < 7
? "red"
: "pink";
}

Siguiendo con lo anterior, donde añadíamos el valor del radio del círculo, vamos a añadir el valor de la magnitud del temblor para poder especificar el color que tendrá el círculo:

import { getColor } from './colors';
...
earthQuakesGipuzkoa.forEach((point) =>
circle([point.location.lat, point.location.lng], {
// 1200 m máximo incluido y 200 el mínimo incluido
radius: Math.floor(Math.random() * (1200 - 200 + 1)) + 200,
// Coloreamos en base el valor de la magnitud
color: getColor(point.magnitude)
}).bindPopup(`
<h3>${point.name}</h3>
<hr>
<h2>${point.magnitude}</h2>
`).addTo(mymap)
);

Con esto, conseguiremos un resultado similar al que se puede ver donde se podrá apreciar que ya tenemos los círculos en diferentes colores:

Añadir colores en base a la magnitud

Llegados a este punto, ya estamos listos para empezar a trabajar con la leyenda.

Añadir información en la leyenda

Como bien sabéis, se puede añadir contenido HTML en el control personalizado que hemos creado anteriormente y lo que haremos es, basándonos en los colores que usamos para pintar los círculos, con los mismo rangos, establecemos el apartado visual.

Vamos al fichero src/main/legend-control.ts y en la función onAdd hacemos cambios dentro del contenedor, en la propiedad innerHTML que sirve para ello, para añadir contenido HTML.

import { DomUtil } from 'leaflet';
import { Util } from 'leaflet';
import { ControlPosition } from 'leaflet';
import { Control } from 'leaflet';
const LegendData = Control.extend({
// Inicialización
...
// Añadir la información para formar el control personalizado
onAdd: function() {
const controlDiv = DomUtil.create('div', 'info legend');
controlDiv.innerHTML = `<h5>${this.options.title}</h5>
<ul>
<li>0 - 1 white</li>
<li>1 - 2 green</li>
<li>2 - 3 #6e8c51</li>
<li>3 - 4 yellow</li>
<li>4 - 5 #f5d142</li>
<li>5 - 6 orange</li>
<li>6 - 7 red</li>
<li>7+ pink</li>
</ul>
`;
...
return controlDiv;
}
});
export const legendData = (options?: {
position?: ControlPosition, title?: string
}) => new LegendData(options);

Si guardamos los cambios y visualizamos en el navegador:

Primera incursión añadiendo datos reales en la leyenda con los rangos

Se pueden ver los nuevos datos, pero no los podemos considerar como correctos, la idea es que en cada punto tengamos un color correspondiente al nivel de magnitud sin tanto texto y más elegante.

Dando estilo y formato elegante a los datos de la leyenda

Añadimos la lista con los rangos que tenemos para hacerlo dinámico y mediante la función para obtener el color, pintaremos mediante CSS dando una apariencia más agradable.

Pondremos los valores de los rangos para que se pinten los colores acorde a lo que se visualizará.

Por ejemplo, para pintar el color blanco (0–1) añadiremos como elemento un valor comprendido entre ese rango, por ejemplo: 0.5, 0.76, 0.2,… Si aplicamos para 6–7 pondremos por ejemplo: 6.78

Quedará dentro de la función onAdd de la siguiente manera:

onAdd: function () {
const controlDiv = DomUtil.create('div', 'info legend');
const magnitudes = [0.5, 1.5, 2.5, 3.5, 4.5, 5.5, 6.5, 8];
const labels = [
'0 >= x < 1',
'1 >= x < 2',
'2 >= x < 3',
'3 >= x < 4',
'4 >= x < 5',
'5 >= x < 6',
'6 >= x < 7',
'7+',
];
// Para añadir las opciones dinámicamente
for (var i = 0; i < magnitudes.length; i++) {
controlDiv.innerHTML +=
'<i style="background:' + getColor(magnitudes[i]) + '"></i>' + labels [i] +'<br>';
}
controlDiv.style.backgroundColor = 'white';
controlDiv.style.textAlign = 'center';
controlDiv.style.padding = '3px';
controlDiv.style.borderRadius = '6px';
controlDiv.style.border = '1px solid green';
controlDiv.style.marginBottom = '5px';
controlDiv.style.width = '100%';
return controlDiv;
},

Una vez aplicados los cambios, recargamos el mapa y se puede apreciar que aparecen los nuevos textos de manera dinámica, pero no los colores. Debemos de darle estilos CSS.

Creamos un nuevo fichero en el directorio main llamado legend.css:

.legend {
line-height: 18px;
color: #555;
}
.legend i {
width: 18px;
height: 18px;
float: left;
margin-right: 8px;
opacity: 0.7;
}

Ahora para poder usar estos estilos, debemos de añadir la referencia en el fichero index.html:

<html>
<head>
<title>02-Mapa con Typescript</title>
<link rel="stylesheet" href="./legend.css">
<link rel="stylesheet" href="./../assets/css/content.css">
<link rel="stylesheet" href="./../assets/css/main.css">
</head>
<body>
<div id="app"></div>
<script src="./index.ts"></script>
</body>
</html>

Y con esto cambios se visualizaría de esta manera:

Resultado final

Como se puede apreciar, ya podemos identificar de que intensidad son los temblores y así es como quedará nuestra leyenda. Si queremos mejorar aspectos de la apariencia lo haremos con CSS y en cuanto al texto para añadirlo de otra manera, cambiar la lista de “labels”.

Con esto queda finalizado el proyecto con el control personalizado de la leyenda.

El código lo podremos encontrar a continuación:

https://github.com/leaflet-maps-course/typescript-basic-example/tree/01-legend-control

Sería interesante que en los comentarios añadáis vuestros comentarios con vuestros ejemplos, ¡¡seguro que podemos aprender de otros ejemplos!!

Presencia en redes sociales.

Podéis encontrarme en las siguientes redes.

--

--

Anartz Mugika Ledo🤗
Anartz Mugika Ledo🤗

Written by Anartz Mugika Ledo🤗

[{#frontend:[#mobile:{#android, #kotlin, #ionic}}, {#web:{#angular, #qwik, #bootstrap}}],{#backend: [{#graphql, #nestjs,#express, #mongodb, #mysql}]}]

Responses (1)