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

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.

--

--

[{#frontend:[#mobile:{#android, #kotlin-java, #ionic}}, {#web:{#angular, #material, #bootstrap}}],{#backend: [{#graphql, #symfony,#express, #mongodb, #mysql}]}]

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store
Anartz Mugika Ledoūü§ó

[{#frontend:[#mobile:{#android, #kotlin-java, #ionic}}, {#web:{#angular, #material, #bootstrap}}],{#backend: [{#graphql, #symfony,#express, #mongodb, #mysql}]}]