web

Tutorial OpenLayers: marcadores, coordenadas, eventos y datos dinámicos

En este post te mostraré un ejemplo de proyecto con OpenLayers en donde se muestran:

  • Introducción a OpenLayers
  • Descargar OpenLayers
  • Creación de mapa
  • Centrado de mapa en determinada ubicación
  • Marcadores personalizados con imagen propia
  • Listener de eventos como click o zoom cambiado
  • Consumo de API PHP para obtener los marcadores

Voy a desglosar cada cosa y al final dejaré el código del proyecto completo.

Introducción a OpenLayers

La librería se define a sí misma (traducida literalmente) como:

Una biblioteca llena de funciones y de alto rendimiento para todas sus necesidades de mapeo

En pocas palabras es una librería para manejar mapas, así como Google maps, pero esta es una alternativa gratuita y open source muy parecida a Google maps.

El punto que más destaca es que es gratis.

Puedes visitar el repositorio en https://github.com/openlayers/openlayers y ver su página web en https://openlayers.org/

Descargar OpenLayers

Si usas npm puedes instalar OpenLayers con:

npm install ol

En caso de que quieras usarlo sin NPM es totalmente posible. Aquí dejo los enlaces de la última versión que también se pueden obtener en unpkg.com

Css: https://unpkg.com/openlayers@4.6.5/dist/ol.css

JavaScript: https://unpkg.com/openlayers@4.6.5/dist/ol.js

Incluye ambos en tu HTML y estamos listos.

Creación y centrado de mapa

Vamos a ver cómo crear, en primer lugar, un mapa, y mostrarlo en un contenedor. En el HTML creamos un div con el id mapa y le damos el estilo necesario.

Después, con JavaScript, creamos el mapa:

const LATITUD_CENTRO = 19.413793,
    LONGITUD_CENTRO =  -99.128145,
    ZOOM = 15;

const mapa = new ol.Map({
    target: 'mapa', // el id del elemento en donde se monta
    layers: [
        new ol.layer.Tile({
            source: new ol.source.OSM()
        })
    ],
    view: new ol.View({
        center: ol.proj.fromLonLat([LONGITUD_CENTRO, LATITUD_CENTRO]),
        zoom: ZOOM,
    })
});

Para crear un mapa creamos una nueva instancia de ol.Map, se va a montar en el elemento con el id mapa en la línea 6.

Después, por defecto, tiene una capa y una vista. En la vista (línea 12) utilizamos las coordenadas para centrarlo en determinada ubicación y le ponemos un zoom de 15.

Montando mapa con zoom y centrado en coordenadas

Marcadores

Podemos crear un marcador, para ello debemos crear una capa. Después, para agregarle el icono se indica el estilo. El marcador va dentro de una capa y finalmente la capa se agrega al mapa con el método addLayer.

El código queda así:

let marcador = new ol.Feature({
    geometry: new ol.geom.Point(
        ol.proj.fromLonLat([-99.12105, 19.419617])// En dónde se va a ubicar
    ),
});

// Agregamos icono
marcador.setStyle(new ol.style.Style({
    image: new ol.style.Icon({
        src: "pizza.png",
    })
}));

// marcadores debe ser un arreglo
const marcadores = []; // Arreglo para que se puedan agregar otros más tarde

marcadores.push(marcador);// Agregamos el marcador al arreglo

let capa = new ol.layer.Vector({
    source: new ol.source.Vector({
        features: marcadores, // A la capa le ponemos los marcadores
    }),
});
// Y agregamos la capa al mapa
mapa.addLayer(capa);

Lo resaltable es que se indican las coordenadas del mapa en la línea 3 y el icono del marcador en la línea 10.

Los marcadores son un arreglo, podemos invocar a push cuantas veces sea necesario para agregar un elemento al arreglo.

La capa se define en la línea 19 con los marcadores en features y finalmente se añade al mapa en la línea 25. Ahora tenemos un bonito marcador de una pizzería:

Agregar marcador a mapa de OpenLayers

Por cierto, la imagen fue tomada de Pixabay.

Eventos

El mapa que acabamos de crear tiene varios eventos. Vamos a ver dos de ellos.

Click

El click es cuando se hace click en un elemento del mapa o en el mapa; con esto podemos detectar si alguien hace click en un marcador.

mapa.on('singleclick', function(evt) {
    var feature = mapa.forEachFeatureAtPixel(evt.pixel, function(feature, layer) {
        // Aquí se puede filtrar la feature
        return feature;
    });
    if (feature) {
        console.log("Click en: ", feature);
    }
});

Cambio de zoom

Del mismo modo podemos escuchar cuando el usuario aleja o acerca el mapa, es decir, cambia el zoom:

let zoomActual = mapa.getView().getZoom();
mapa.on('moveend', function(e) {
    let nuevoZoom = mapa.getView().getZoom();
    if (zoomActual != nuevoZoom) {
        console.log('Nuevo zoom: ' + nuevoZoom);
        zoomActual = nuevoZoom;
    }
});

Consumo de API con PHP para marcadores dinámicos

Es momento de mostrar el pequeño proyecto o demostración. He colocado un select de categorías que sirve para ver categorías en el mapa. En el change del select hago una petición http con fetch a mi servidor con PHP y obtengo las coordenadas de los marcadores para dibujarlos.

Vamos parte por parte. Primero veamos el código HTML:

<!doctype html>
<html lang="es">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width, initial-scale=1,
            shrink-to-fit=no">
        <meta name="author" content="Parzibyte">
        <title>Demostración OpenLayers</title>

        <link rel="stylesheet" href="css/bootstrap.min.css">
        <link rel="stylesheet" href="css/ol.css">
        <link rel="stylesheet" href="css/styles.css">
    </head>
    <body>
        <main role="main" class="container-fluid">
            <div class="row">
                <div class="col-12">
                    <h1>Demostración mapas con OpenLayers</h1>
                </div>
                <div class="col-12 mb-2">
                    <div class="form-group">
                        <label for="selectCategorias">Categoría:</label>
                        <select class="form-control" id="selectCategorias">
                            <option value="veterinarias">Veterinarias</option>
                            <option value="pizzerias">Pizzerías</option>
                        </select>
                    </div>
                </div>
                <div class="col-12">
                    <div id="mapa"></div>
                </div>
            </div>
        </main>
        <script type="text/javascript" src="js/ol.js"></script>
        <script type="text/javascript" src="js/script.js"></script>
    </body>
</html>

Existe la lista desplegable y el div con el id mapa. Estoy usando Bootstrap 4 para maquetar. Ahora veamos el script:

let ultimaCapa; // Para removerla cada vez que se selecciona otra categoría


const LATITUD_CENTRO = 19.413793,
    LONGITUD_CENTRO = -99.128145,
    ZOOM = 15;

const mapa = new ol.Map({
    target: 'mapa', // el id del elemento en donde se monta
    layers: [
        new ol.layer.Tile({
            source: new ol.source.OSM()
        })
    ],
    view: new ol.View({
        center: ol.proj.fromLonLat([LONGITUD_CENTRO, LATITUD_CENTRO]),
        zoom: ZOOM,
    })
});

const refrescarMapaConCategoria = categoria => {
    fetch(`coordenadas.php?categoria=${categoria}`)
        .then(datos => datos.json())
        .then(coordenadasConIcono => {
            dibujarMarcadoresEnMapa(coordenadasConIcono);
        });
};

const dibujarMarcadoresEnMapa = coordenadasConIcono => {
    if (ultimaCapa) {
        mapa.removeLayer(ultimaCapa);
    }
    const { icono, coordenadas } = coordenadasConIcono;

    const marcadores = [];
    coordenadas.forEach(coordenada => {
        let marcador = new ol.Feature({
            geometry: new ol.geom.Point(
                ol.proj.fromLonLat([coordenada.longitud, coordenada.latitud])
            ),
        });
        marcador.setStyle(new ol.style.Style({
            image: new ol.style.Icon(({
                src: icono,
            }))
        }));
        marcadores.push(marcador);
    });
    ultimaCapa = new ol.layer.Vector({
        source: new ol.source.Vector({
            features: marcadores,
        }),
    });
    mapa.addLayer(ultimaCapa);
};


const $select = document.querySelector("#selectCategorias");
const obtenerCategoriaSeleccionada = () => $select.options[$select.selectedIndex].value;

const refrescarMapaConCategoriaSeleccionada = () => refrescarMapaConCategoria(obtenerCategoriaSeleccionada())
// Cuando seleccionen otra opción, refrescar el mapa
$select.addEventListener("change", refrescarMapaConCategoriaSeleccionada);


// Algunos eventos que podrían ser de utilidad

mapa.on('singleclick', function(evt) {
    var feature = mapa.forEachFeatureAtPixel(evt.pixel, function(feature, layer) {
        // Aquí se puede filtrar la feature
        return feature;
    });
    if (feature) {
        console.log("Click en: ", feature);
    }
});



let zoomActual = mapa.getView().getZoom();
mapa.on('moveend', function(e) {
    let nuevoZoom = mapa.getView().getZoom();
    if (zoomActual != nuevoZoom) {
        console.log('Nuevo zoom: ' + nuevoZoom);
        zoomActual = nuevoZoom;
    }
});

// Al inicio de todo, obtener con la primer categoría
refrescarMapaConCategoriaSeleccionada();

Lo único que se hace es escuchar el cambio de selección del elemento del select; cuando eso sucede se hace una petición y se obtienen los marcadores.

Como se está refrescando el mapa en cada cambio, debo obtener una referencia a la capa de los marcadores para removerla en cada actualización.

Finalmente veamos el servidor de PHP que por el momento muestra información a partir de un arreglo, pero podría fácilmente conectarse a una base de datos.

<?php
if (empty($_GET["categoria"])) {
    exit("No hay categoría");
}

$categoria = $_GET["categoria"];
#TODO: hacerlo en una BD
$veterinarias = [
    [
        "latitud" => 19.419617,
        "longitud" => -99.121051,
    ],
    [
        "latitud" => 19.418919,
        "longitud" => -99.134218,
    ],
    [
        "latitud" => 19.411699,
        "longitud" => -99.127711,
    ],
];
$pizzerias = [
    [
        "latitud" => 19.413955,
        "longitud" => -99.115889,
    ],
    [
        "latitud" => 19.413858,
        "longitud" => -99.138855,
    ],
    [
        "latitud" => 19.421102,
        "longitud" => -99.126836,
    ],
];

if ($categoria === "veterinarias") {
    echo json_encode([
        "icono" => "veterinaria.png",
        "coordenadas" => $veterinarias,
    ]);
} else {
    echo json_encode([
        "icono" => "pizza.png",
        "coordenadas" => $pizzerias,
    ]);
}

Es un simple if, que podría remplazarse por una consulta usando where. Por favor observa que estoy devolviendo el icono y las coordenadas, así fácilmente se puede configurar el diseño de cada marcador.

Aquí el resultado final:

Demostración openlayers con marcadores y coordenadas

Conclusión

Espero que todo haya quedado claro, fue un proyecto simple pero en donde aprendí mucho. Creo que con esto es suficiente para crear una aplicación web simple que utilice mapas y no requiera el poder de Google Maps.

Dejo el enlace al repositorio de GitHub para que puedas explorarlo.

Estoy aquí para ayudarte 🤝💻


Estoy aquí para ayudarte en todo lo que necesites. Si requieres alguna modificación en lo presentado en este post, deseas asistencia con tu tarea, proyecto o precisas desarrollar un software a medida, no dudes en contactarme. Estoy comprometido a brindarte el apoyo necesario para que logres tus objetivos. Mi correo es parzibyte(arroba)gmail.com, estoy como@parzibyte en Telegram o en mi página de contacto

No te pierdas ninguno de mis posts 🚀🔔

Suscríbete a mi canal de Telegram para recibir una notificación cuando escriba un nuevo tutorial de programación.
parzibyte

Programador freelancer listo para trabajar contigo. Aplicaciones web, móviles y de escritorio. PHP, Java, Go, Python, JavaScript, Kotlin y más :) https://parzibyte.me/blog/software-creado-por-parzibyte/

Ver comentarios

Entradas recientes

Creador de credenciales web – Aplicación gratuita

Hoy te voy a presentar un creador de credenciales que acabo de programar y que…

5 días hace

Desplegar PWA creada con Vue 3, Vite y SQLite3 en Apache

Ya te enseñé cómo convertir una aplicación web de Vue 3 en una PWA. Al…

2 semanas hace

Arquitectura para wasm con Go, Vue 3, Pinia y Vite

En este artículo voy a documentar la arquitectura que yo utilizo al trabajar con WebAssembly…

2 semanas hace

Vue 3 y Vite: crear PWA (Progressive Web App)

En un artículo anterior te enseñé a crear un PWA. Al final, cualquier aplicación que…

2 semanas hace

Errores de Comlink y algunas soluciones

Al usar Comlink para trabajar con los workers usando JavaScript me han aparecido algunos errores…

2 semanas hace

Esperar promesa para inicializar Store de Pinia con Vue 3

En este artículo te voy a enseñar cómo usar un "top level await" esperando a…

2 semanas hace

Esta web usa cookies.