Angular Leaflet Routing

Angular Leaflet Routing

Para este ejemplo debemos primero instalar lo necesario para poder trabajar con Leaflet en Angular o Ionic + Angular

npm install leaflet leaflet-routing-machine leaflet-gesture-handling

Añadimos el CSS necesario en el archivo index.html

<link rel="stylesheet" href="./assets/css/leaflet-gesture-handling.min.css" type="text/css">
    <link rel="stylesheet" href="./assets/css/leaflet-search.css" type="text/css">
    <link rel="stylesheet" href="./assets/css/leaflet-distance-marker.css" type="text/css">
    <link rel="stylesheet" href="./assets/css/leaflet-routing-machine.css" />

Y luego del body añadimos los scripts externos, esto porque al usar los paquetes npm de estas librerias no me funcionaba por lo que la version cdn si funciona.

<script src="./assets/js/leaflet-geometryutil.js"></script>
<script src="./assets/js/leaflet-distance-marker.js"></script>

Hecho esto creamos un componente con el comando:

ng generate component simpleRouting

Luego de eso en el archivo HTML creamos un elemento "div" que contenga el mapa.

<div [id]="id" [class]="className"></div>

Ahora si podemos concentrarnos en el archivo typescript Primero debemos importar todo lo necesario para el ejercicio, en este caso las librerias de leaflet, un servicio para obtener la ubicación actual, un servicio que entrega un marcador personalizado, y una interfaz para el tipado de typescript.

import 'leaflet';
import 'leaflet-routing-machine';
import { Component, OnInit, Input } from '@angular/core';
import { MapService } from "src/app/services/map.service";
import { LocalizationService } from "src/app/services/localization.service";
import { GestureHandling } from "leaflet-gesture-handling";
import { environment } from 'src/environments/environment';
import { IUbication } from "src/app/interfaces/models";

declare let L: any;

@Component({
    selector: 'simple-routing-map',
    templateUrl: './simple-routing-map.component.html',
    styleUrls: ['./simple-routing-map.component.scss'],
})
export class SimpleRoutingMapComponent implements OnInit {

Hecho esto en la clase especificamos algunos parámetros o propiedades que tendrá el componente como el id, la clase, el nivel de zoom, las coordenadas de destino, habilitar los gestos del mapa y la opción de manejar la ruta con una polilinea o con leaflet routing machine

    @Input() id: string;
    @Input() className = '';
    @Input() zoom = 16;
    @Input() destinationCoords: IUbication;
    @Input() enableGesture = false;
    @Input() usePolyline = true;

Hecho esto las demas variables que usamos son para guardar la polilinea, el mapa, la capa de marcadores, la coordenada actual, el array de coordenadas para el enrutamiento y para saber si el mapa ya cargo.

    polylineRoute: any;
    map: any;
    mapMarkers: any[] = null;
    mapIsLoaded = false;
    markersLayer = new L.LayerGroup();
    currentCoordinate: any = null;
    routeControl: any;
    arrRoutesLatLng = [];

    constructor(
        private mapService: MapService,
        private localizationService: LocalizationService
    ) {

    }

    async ngOnInit() { }

Ejecutamos el código en el ciclo "AfterViewInit" para evitar problemas de renderización. Primero obtenemos los marcadores disponibles, luego esperamos que el servicio de localización me devuelva mis coordenadas actuales y al final inicializamos el mapa.

    async ngAfterViewInit() {
        // Obtener marcadores
        this.mapMarkers = await this.mapService.getMarkers().toPromise();
        // Obtener Coordenadas
        this.currentCoordinate = await this.localizationService.getCoordinate();
        // Inicializar el Mapa
        await this.initializeMap();
    }

En esta función habilitamos/deshabilitamos los gestos del mapa. Luego seteamos el valor de las coordenadas para el enrutamiento, creamos el mapa, le agregamos algunos eventos y configuramos la capa del Mapa. Luego añadimos los marcadores en el mapa y al final realizamos el enrutamiento sea con una polilinea o con el routing de Leaflet Routing Machine.

    async initializeMap() {
        if (this.enableGesture) {
            L.Map.addInitHook("addHandler", "gestureHandling", GestureHandling);
        }
        //Setear las Coordenadas de tipo LatLng
        this.arrRoutesLatLng[0] = this.createLatLng(this.currentCoordinate.latitude, this.currentCoordinate.longitude);
        this.arrRoutesLatLng[1] = this.createLatLng(this.destinationCoords.latitude, this.destinationCoords.longitude);
        // Crear el Mapa
        this.map = L.map(this.id, {
            gestureHandling: this.enableGesture,
            zoomAnimation: true,
            markerZoomAnimation: true,
            zoomControl: true
        });
        // Agregar Evento al Mapa cuando esta cargado
        this.map.on('load', (e: any) => {
            this.mapIsLoaded = true;
            // Invalidar Tamanio
            this.map.invalidateSize();
        });
        this.map.zoomControl.setPosition('topright');
        // Configurar la vista centrada
        this.map.setView([-0.1548643, -78.4822049], this.zoom);
        // Agregar la capa del Mapa
        L.tileLayer(environment.mapLayers.google.url, {
            attribution: environment.mapLayers.google.attribution,
            maxZoom: 18,
            updateWhenIdle: true,
            reuseTiles: true
        }).addTo(this.map);
        //Añadir la Ruta en caso de ser necesario
        if (!this.usePolyline) {
            this.setRoutingMachine(this.arrRoutesLatLng);
        }
        this.map.addLayer(this.markersLayer);
        // Si obtuve coordenadas añadir el marcador
        if (this.currentCoordinate) {
            const iconCurrent = await this.mapService.getCustomIcon('red');
            let currentPoint: any;
            this.arrRoutesLatLng[0] = this.createLatLng(this.currentCoordinate.latitude, this.currentCoordinate.longitude);
            if (iconCurrent) {
                currentPoint = new L.Marker(this.arrRoutesLatLng[0], { icon: iconCurrent, title: 'Mi Posición Actual' });
            } else {
                currentPoint = new L.Marker(this.arrRoutesLatLng[0], { title: 'Mi Posición Actual' });
            }
            currentPoint.bindPopup('Mi Ubicación Actual').openPopup();
            this.markersLayer.addLayer(currentPoint);
        }
        //Añadir el destino final
        let punto = null;
        const markerIcon = await this.mapService.getCustomIcon('green');

        if (markerIcon) {
            punto = new L.Marker(this.arrRoutesLatLng[1], { icon: markerIcon });
        } else {
            punto = new L.Marker(this.arrRoutesLatLng[1]);
        }
        // Añadir el punto a la capa de marcadores
        this.markersLayer.addLayer(punto);
        //Añado la polilinea de ser necesario
        if (this.usePolyline) {
            this.addPolyline(this.arrRoutesLatLng);
        }

    }

Esta función sirve para añadir la polilinea al mapa pasandole como parámetros el array de coordenadas y al final centramos el mapa.

    addPolyline(arrayCoordsLatLng: any) {
        this.polylineRoute = L.polyline(arrayCoordsLatLng,
            {
                color: '#ee0033',
                weight: 8,
                opacity: .8,
                dashArray: '20,15',
                lineJoin: 'round'
            }
        );
        //Añadir Ruta Polyline
        this.markersLayer.addLayer(this.polylineRoute);
        this.map.fitBounds(this.polylineRoute.getBounds());
    }

Esta función añade la ruta con Leaflet Routing Machine usando como Router el Api de Mapbox para evitar el limite de ORSM.

    setRoutingMachine(arrCoords: any) {
        this.routeControl = L.Routing.control({
            waypoints: arrCoords,
            show: false,
            routeWhileDragging: false,
            router: new L.Routing.mapbox(environment.mapBoxApiKey),
            createMarker: function () { return null; }
        });
        this.routeControl.addTo(this.map);
    }

Esta función sirve para actualizar las coordenadas de la ruta.

    updateRouteCoords(arrCoords: any[]) {
        this.routeControl.setWaypoints(arrCoords);
    }

Esta función create un array LatLng con Leaflet.

    createLatLng(latitude: number, longitude: number) {
        return L.latLng(latitude, longitude);
    } 
}

Nota

Si desean probar este código pronto espero subirlo en Stackblitz pero ustedes mismo pueden probarlo creando un entorno, es gratis y sirve mucho para no usar los recursos de su PC.

Para llamar al componente lo haces de la siguiente manera:

 <simple-routing-map [zoom]="11" className="public-service-map"
            id="public-service-map-detail"
            [destinationCoords]="coordsArr" [usePolyline]="false"></simple-routing-map>

Imágenes

map_routing_polyline.jpg

map_routing_machine.jpg