Construye mapas con cluster de marcadores usando Angular y Leaflet

Construye mapas con cluster de marcadores usando Angular y Leaflet

Es sorprendente la cantidad existente hoy de soluciones tecnológicas que utilizan datos geográficos. Sabemos que esos datos geográficos, por sí mismos, no tienen ningún sentido, ya que necesitan un procesamiento inteligente y, de manera amigable, presentarse como una solución a un problema para los usuarios.

Seguro ya debes imaginar de lo que estoy hablando, ¿verdad? Después de todo, tenemos aplicaciones que miles de personas usan diariamente, como Uber (Transporte), Google Maps/Waze (GPS), Ifood (Delivery). Incluso en el área de entretenimiento, Pokémon Go es un gran ejemplo.

Por tanto, puedo decir que, sin duda, hay varios problemas que podrían solucionarse perfectamente con la geolocalización.


Visión General


Para nosotros, los desarrolladores, siempre es importante tener conocimiento de las herramientas adecuadas para solucionar los diferentes problemas que se nos presentan en nuestro día a día. En este artículo, presentaré de manera práctica cómo implementar un mapa con clustering de puntos geográficos, dentro de tu proyecto desarrollado en Angular.

Clusterización de Marcadores

En general, el término clusterización o clustering se utiliza para referirse al acto de agrupar, categorizar, combinar dos o más recursos con el objetivo de tener un resultado final optimizado. Para el contexto de la información geográfica, la idea es, dependiendo del nivel de zoom, agrupar dos o más puntos geográficos más cercanos, permitiendo ver solo un punto, pero con la cantidad que ese punto representa, como se muestra en la imagen a continuación.

Figura 1: Ejemplo de clusterización de puntos geográficos.

Esta técnica se utiliza con bastante frecuencia en sistemas que proporcionan algún tipo de visualización geográfica de grandes volúmenes de datos estadísticos (hoy ya definido el concepto como Geo Big Data).

¿Te imaginas el costo computacional e incluso la contaminación visual que supondría tener miles de puntos trazados en la misma región en un mapa?

A estas alturas creo que debes haber entendido la importancia de esta técnica, ¿verdad? Sin más preámbulos, conozcamos y pongamos en práctica el uso de estas herramientas.

Leaflet y su extensión Leaflet.markercluster


Leaflet es una biblioteca open-source JavaScript creada en 2010, bastante usada en la producción de mapas interativos. Por su parte, Leaflet.markercluster es una extensión/plugin que implementa el comportamento visual de clusterización de los puntos geográficos.


Instalación y configuración


Con nuestro manejador de paquetes npm, vamos a separar la instalación, yendo primero con la biblioteca Leaflet y sus dependencias, para luego continuar con el plugin Leaflet.markercluster y sus respectivas dependencias.

npm install leaflet

npm install @asymmetrik/ngx-leaflet

npm install --save-dev @types/leaflet

La última dependencia se usa para definir el tipo para usar en la codificación en TypeScript, por lo que no es necesario usarlo en producción, solo en desarrollo, y por esta razón se usa el flag --save-dev en el comando de instalación.

Seguimos los mismos pasos para la instalación del plugin Leaflet.markercluster:

npm install leaflet.markercluster

npm install @asymmetrik/ngx-leaflet-markercluster

npm install --save-dev @types/leaflet.markercluster


Después de instalar las dependencias anteriores, ve al archivo angular.json y agrega las referencias a los archivos de estilo .css de ambas bibliotecas dentro de la propiedad de estilos:

"./node_modules/leaflet/dist/leaflet.css",

"./node_modules/leaflet.markercluster/dist/MarkerCluster.Default.css"

Al final, el archivo debe tener una estructura similar al siguiente fragmento:

...

"projects":{

...

"arquitect":{

"build": {

...

"options":{

...

"styles": [

...

"./node_modules/leaflet/dist/leaflet.css",

"./node_modules/leaflet.markercluster/dist/MarkerCluster.Default.css"

],

}

}

}

}


Ahora puedes importar los módulos instalados desde las dependencias ngx-leaflet y ngx-leaflet-markercluster, dentro del módulo donde desea usar esta funcionalidad (en AppModule, por ejemplo), y también agregarlo al array de imports como se muestra a continuación:


imports: [

...,

LeafletModule,

LeafletMarkerClusterModule

],


Si completaste todos los pasos de configuración anteriores, entonces tu aplicación está lista para implementarse.


Implementación


Aquí comenzaremos la parte lógica del componente (arquivo .ts) para luego incluir el contenido del template (arquivo .html) y de estilo .css.

Primero vamos a declarar las variables necesarias para el funcionamiento de nuestro componente. Para eso, debemos importar el módulo de la biblioteca Leaflet y sus funciones principales, que también usaremos.

import * as L from 'leaflet';

import { icon, latLng, marker } from 'leaflet';


La parte inicial del componente debería verse así:

export class AppComponent {

map!: L.Map;

markerClusterGroup!: L.MarkerClusterGroup;

markerClusterData = [];

options = {

layers: [

L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {

attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>'

})

],

zoom: 4,

center: { lat: -14.4095261, lng: -51.31668 }

}

ngOnInit(){

this.markerClusterGroup = L.markerClusterGroup({removeOutsideVisibleBounds: true});

}

...

}


Sobre el apartado anterior, me gustaría dejar algunas observaciones. El signo ! se agregó para suprimir una advertencia de TypeScript que requiere la inicialización de las respectivas variables junto con la declaración. Pero no te preocupes, porque justo debajo se garantiza que estas variables se inicializarán.

El objeto option es donde definimos la referencia para la capa del mapa del mundo. En este caso, opté por OpenStreetMap, pero puede ser reemplazado por otra fuente como Google Maps, Bing Maps, entre otras disponibles.

En cuanto a las propiedades zoom y center, fue la combinación de valores que encontré y pude enmarcar mejor todo el territorio brasileño. En la siguiente sección, comprenderás mejor la razón de encuadrar a Brasil en el mapa.

Hecho esto, pasamos a obtener los datos de los puntos geográficos que usaremos como marcadores.

Visualización de aeropuertos/pistas de aterrizaje de Brasil


Como brasileño, tan pronto como pensé en esta funcionalidad, fui en busca de información interesante que pudiera presentar como un ejemplo utilizando datos de mi país. Por supuesto, tampoco podría ser algo complejo que de alguna manera pudiera confundir a los lectores. Fue entonces cuando encontré la API https://airlabs.co/ que proporciona las posiciones geográficas (latitud y longitud) de aeropuertos y pistas de aterrizaje en Brasil.

Es bastante simple de usar: solo regístrate y proporciona una clave para consultar la API. Para no extender demasiado este artículo, guardé en un archivo .json el resultado de la solicitud en la API (realizada en el endpoint https://airlabs.co/api/v9/airports?country_code=BR&api_key=[key_api_aquí ]), ya que también dejaré disponible el repositorio para ser probado (y no exceder la cuota de solicitudes, ya que la API tiene un límite).

De esa forma, volvemos a nuestro componente para hacer algunos ajustes. Pero primero, es importante verificar si la propiedad "resolveJsonModule" está configurada como verdadera en el archivo tsconfig.json, de la siguiente manera:

{

"compileOnSave": false,

"compilerOptions": {

...,

"resolveJsonModule": true

},

...

}    

Entonces, podemos importar el archivo que contiene las ubicaciones dentro del componente:

import * as geoJsonData from '../assets/brazil_airports.json';

También se debe declarar una variable con el contenido del archivo:

brazilAirportsMarkers: any = geoJsonData;

Crea un método para leer los datos y generar los marcadores que se agregarán al mapa, según las coordenadas geográficas. Y es aquí donde también usamos las funciones icon, latLng y marker que importamos anteriormente:

initMarkers() {

this.brazilAirportsMarkers.response.forEach( (item:any) => {

const mapIcon = this.getDefaultIcon();

const coordinates = latLng([item.lat, item.lng]);

let layer = marker(coordinates).setIcon(mapIcon);

this.markerClusterGroup.addLayer(layer);

});        

this.addLayersToMap();

}  

private getDefaultIcon() {

return icon({

iconSize: [25, 41],

iconAnchor: [13, 41],

iconUrl: 'assets/marker-icon.png'

});

}

private addLayersToMap() {

this.markerClusterGroup.addTo(this.map);

}

Finalmente, incluyamos también el método que se activa al activar el evento leafletMapReady, que a su vez llama al método initMarkers(), responsable de generar los marcadores:

onMapReady($event: L.Map) {

this.map = $event;

this.initMarkers();

}  

El contenido del archivo de template debería verse así:

<div class="map-container"

leaflet

[leafletOptions]="options"

(leafletMapReady)="onMapReady($event)"

[leafletMarkerCluster]="markerClusterData">

</div>

Luego cerramos con el contenido del archivo de estilo .css que, como es solo una propiedad dentro de una clase, podría agregarse fácilmente inline.

.map-container {

height: 400px;

}

Con eso, simplemente ejecuta el comando npm serve y tendremos este hermoso mapa con agrupación de marcadores:

Figura 2: Clusterización de marcadores donde están ubicados los aeropuertos y pistas de Brasil.

Puedes consultar el código completo a través del repositorio que creé en mi GitHub.


Conclusión

Por supuesto, la biblioteca Leaflet cuenta con una variedad de plugins que permiten enriquecer aún más la interacción y la experiencia del usuario con la visualización de información geográfica. Tal vez en un próximo artículo podamos explorar y presentar otra característica interesante.

Espero que hayas comprendido el propósito de este artículo de una manera sencilla e intuitiva. Si tienes alguna duda, sugerencia o crítica, te dejo mi correo aquí (eidercarlos@gmail.com). No dudes en contactarme.

¡Abrazos!

💡
Las opiniones y comentarios emitidos en este artículo son propiedad única de su autor y no necesariamente representan el punto de vista de Listopro.

Listopro Community da la bienvenida a todas las razas, etnias, nacionalidades, credos, géneros, orientaciones, puntos de vista e ideologías, siempre y cuando promuevan la diversidad, la equidad, la inclusión y el crecimiento profesional de los profesionales en tecnología.