Mapas Interativos open-source com Leaflet

O Leaflet é uma biblioteca JavaScript open-source voltada para a criação de mapas interativos. Ela é responsável apenas pela renderização e pela interação do mapa.

Na prática, o Leaflet consome imagens de mapa, conhecidas como tiles, fornecidas por serviços externos. Essas imagens são organizadas em uma grade, formando o mapa completo na tela.

A partir disso, a biblioteca permite adicionar elementos interativos, como: markers (marcadores), linhas e polígonos, popups, eventos de clique e interação.

Um ponto importante: o Leaflet não possui dados geográficos próprios. Para funcionar, ele depende de uma fonte externa de tiles, como serviços de mapas online.

OpenStreetMap: de onde vêm os dados dos mapas

Como o Leaflet não possui dados geográficos próprios, é necessário utilizar uma fonte externa. Neste exemplo, vamos usar o OpenStreetMap, um banco de dados geográfico mantido por uma comunidade global de voluntários.

O OpenStreetMap é baseado em dados abertos, ou seja, qualquer pessoa pode utilizar as informações livremente, desde que dê os devidos créditos ao projeto e aos seus contribuidores. Na prática, vamos configurar o OpenStreetMap como provedor de tiles do nosso mapa, veremos isso mais adiante no código.

⚠️ Importante: O OpenStreetMap possui limitações quanto ao volume de requisições. Por isso, ele é mais indicado para: projetos de pequeno porte ou aplicações em desenvolvimento ou testes. Para aplicações com alto volume de acessos, o ideal é utilizar um provedor de tiles dedicado (geralmente pago), que ofereça maior desempenho e escalabilidade.

Configurando o mapa com Leaflet

Para exibir um mapa com o Leaflet, o primeiro passo é incluir a biblioteca no projeto. A forma mais simples de fazer isso é utilizando um CDN.

Na prática, basta adicionar os arquivos de CSS e JavaScript do Leaflet diretamente no HTML.

<link rel="stylesheet" href="https://unpkg.com/leaflet@1.9.4/dist/leaflet.css"
        integrity="sha256-p4NxAoJBhIIN+hmNHrzRCf9tD/miZyoHS5obTRR9BMY="
        crossorigin=""/>
<script src="https://unpkg.com/leaflet@1.9.4/dist/leaflet.js"
         integrity="sha256-20nQCchB9co0qIjJZRGuk2/Z9VM+kNiyxNV1lvTlZBo="
        crossorigin=""></script>

Outra opção é instalar o Leaflet utilizando o NPM, o que é mais comum em projetos que utilizam ferramentas de build como Webpack ou Vite. Para instalar o ele via NPM, execute o seguinte comando no terminal:

npm install leaflet

Após instalar o Leaflet, você pode importar a biblioteca e o CSS diretamente no seu arquivo JavaScript:

// main.js

import L from 'leaflet';
import 'leaflet/dist/leaflet.css';

⚠️ Atenção: É fundamental garantir que o arquivo leaflet.css seja carregado corretamente. Sem ele, elementos como os controles de zoom e os ícones dos marcadores não serão exibidos, um problema bastante comum.

Com a biblioteca configurada, o próximo passo é criar o elemento HTML que irá conter o mapa. Para isso, utilizamos uma div simples.

<div id="map" style="width: 600px;height: 400px;"></div>

Agora vamos criar o mapa, definindo um ponto inicial e o nível de zoom. Neste exemplo, utilizamos as coordenadas -30.036808837888316, -51.21598009185646, que correspondem ao Parque da Redenção, em Porto Alegre.

Também definimos o nível de zoom como 15, o que proporciona uma visualização mais detalhada da região.

var map = L.map('map').setView([-30.036808837888316, -51.21598009185646], 15);

Observe que passamos o id do elemento HTML para o método L.map(), indicando onde o mapa será renderizado. Em seguida, utilizamos o método setView() para definir a posição inicial do mapa. Esse método recebe dois parâmetros: um array com latitude e longitude (lat, lng), o nível de zoom

Se for necessário alterar a posição ou o zoom dinamicamente, basta chamar novamente o método setView() no objeto map, informando os novos valores.

O próximo passo é adicionar uma tile layer, que é a camada responsável pelas imagens do mapa.

É aqui que configuramos o endpoint que o Leaflet utilizará para buscar os tiles e montar o mapa na tela. Como vimos anteriormente, vamos usar o OpenStreetMap como provedor dessas imagens.

L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', {
    maxZoom: 19,
    attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>'
}).addTo(map);

Chamamos o método L.tileLayer() informando o template de URL do serviço de tiles do OpenStreetMap e um objeto de configuração com as opções da camada. Nesse objeto, definimos: maxZoom como 19, limitando o nível máximo de zoom, attribution, responsável por exibir os créditos exigidos pelo OpenStreetMap. Por fim, utilizamos o método addTo(map) para adicionar a camada ao mapa criado anteriormente.

Você conferir ver o exemplo de um mapa simples aqui.

Adicionando marcadores, círculos e polígonos

Podemos adicionar diferentes elementos ao mapa, sendo o mais comum o marcador, utilizado para indicar um ponto específico.

Para adicioná-lo, basta utilizar o método L.marker(), informando um array com as coordenadas de latitude e longitude. Em seguida, chamamos addTo(map) para inserir o marcador no mapa desejado.

var marker = L.marker([-30.036808837888316, -51.21598009185646]).addTo(map);

Outro elemento bastante comum é o círculo, geralmente utilizado para representar uma área a partir de um ponto central.

Para criá-lo, utilizamos o método L.circle(), informando as coordenadas de latitude e longitude. O raio deve ser passado como um segundo parâmetro inteiro (em metros) ou por meio de um objeto de configuração, que também permite personalizar a aparência do círculo.

Nesse objeto, podemos ajustar propriedades como a cor da borda(color), a cor de preenchimento(fillColor), o nível de transparência (fillOpacity) e o raio(radius), que sempre é definido em metros a partir do ponto central.

Após criar o círculo, podemos encadear a chamada do método addTo(map) para adicioná-lo ao mapa desejado.

var circle = L.circle([-30.036808837888316, -51.21598009185646], 400).addTo(map);

// ou

var circle = L.circle([-30.036808837888316, -51.21598009185646], {
    color: '#FF0000',
    fillColor: '#FF0033',
    fillOpacity: 0.5,
    radius: 400
}).addTo(map);

Quando precisamos representar uma área mais personalizada no mapa, podemos utilizar um polígono.

Para isso, usamos o método L.polygon(), informando um array com as coordenadas que definem os pontos da área. Assim como no círculo, também é possível passar um objeto de configuração para personalizar a aparência do polígono.

 var polygon = L.polygon([
    [-30.033902795609123, -51.221540632725805],
    [-30.032405440492138, -51.22047848211876],
    [-30.033640294462014, -51.21568410639647],
    [-30.0362672518493, -51.2103433760591],
    [-30.03989256949555, -51.21698907291952],
]).addTo(map);

Você pode conferirum exemplo de mapas com marcadores, círculo e poligono.

Adicionando popups ao mapa

Podemos adicionar um popup a um elemento do mapa utilizando o método bindPopup("<mensagem>"). Por padrão, o popup só é exibido quando o usuário clica no objeto ao qual ele foi associado.

Se quisermos que o popup já apareça aberto ao carregar o mapa, basta encadear a chamada do método openPopup().

marker.bindPopup("Parque da Redenção").openPopup();

O conteúdo de um popup aceita HTML, o que permite enriquecer a mensagem com diferentes elementos, como <strong> para destaque de texto ou até imagens com <img>.

circle.bindPopup("<div><strong style='color:#FF0000'>Parque da Redenção</strong></div> <p>Visite!</p>").openPopup();

polygon.bindPopup("<div><strong>Parque da Redenção</strong></div> <div><img src='foto.jpg' style='width:100%'></div>").openPopup();

Confira um exemplo de mapas com elementos que utilizam popups.

Também podemos adicionar um popup diretamente no mapa utilizando o método L.popup().

Nesse caso, encadeamos alguns métodos para configurar o comportamento: setLatLng() define as coordenadas onde o popup será exibido, setContent() define o conteúdo e openOn(map) exibe o popup no mapa desejado.

var popup = L.popup()
    .setLatLng([-30.036808837888316, -51.21598009185646])
    .setContent("Um popup direto no mapa")
    .openOn(map);

Trabalhando com eventos no Leaflet

O usuário pode interagir com o mapa, gerando eventos que podem ser capturados pela aplicação.

No Leaflet, utilizamos o método on() do objeto do mapa para ouvir esses eventos. Esse método recebe o nome do evento e uma função que será executada quando ele ocorrer. Essa função, por sua vez, recebe um objeto com as informações do evento.

Um dos eventos mais comuns de trabalhar é o de click no mapa.

var marker = null;
map.on('click', function(e){

    if(marker !== null && map.hasLayer(marker)){
        map.removeLayer(marker)
    }
    document.querySelector('#lat').innerText = e.latlng.lat;
    document.querySelector('#lng').innerText = e.latlng.lng;

     marker = L.marker([e.latlng.lat, e.latlng.lng]).addTo(map);
});

No exemplo acima, utilizamos o método on() para tratar o evento de click. A cada clique no mapa, removemos o marcador anterior, adicionamos um marcador na posição selecionada e atualizamos dois elementos do DOM com as coordenadas do ponto.

Primeiro, verificamos se já existe um marcador e se ele está presente no mapa. Para isso, utilizamos o método hasLayer(). Caso exista, removemos o marcador com removeLayer().

Em seguida, acessamos as coordenadas do ponto clicado através do objeto de evento (e.latlng) e utilizamos esses valores para atualizar os elementos com os ids #lat e #lng.

Por fim, criamos um novo marcador com as coordenadas capturadas e o adicionamos ao mapa.

Confira um exemplo de mapa com o tratamento de eventos.

Como obter a localização do usuário

O Leaflet possui suporte nativo à API de geolocalização do navegador (Geolocation API). Com isso, é possível obter a localização do usuário e centralizar o mapa de forma simples.

Para isso, utilizamos o método locate(), que permite solicitar a posição do usuário, passando algumas configurações.

map.locate({
    setView: true,   // centraliza o mapa automaticamente
    maxZoom: 15,     // zoom máximo ao centralizar
    watch: false      // true = atualiza continuamente (GPS ao vivo)
});

O método locate() tenta obter a posição do usuário e dispara eventos conforme o resultado da operação.

Se a localização for obtida com sucesso, o evento locationfound é acionado. Caso ocorra algum erro, como falta de permissão, o evento locationerror é disparado.

No caso de sucesso, a função de tratamento recebe um objeto com os dados da localização, incluindo latlng (latitude e longitude) e accuracy, que representa a precisão da posição em metros.

 map.on('locationfound', function(e){
    const raio = e.accuracy; // precisão em metros

    L.marker(e.latlng)
        .addTo(map)
        .bindPopup(`Você está aqui! (±${Math.round(raio)}m)`)
        .openPopup();

        L.circle(e.latlng, { radius: raio }).addTo(map);
});

map.on('locationerror', function(e){
    alert('Localização indisponível: ' + e.message);
});

No exemplo acima, tratamos o evento locationfound adicionando um marcador na posição obtida a partir do objeto de evento. Também desenhamos um círculo com o mesmo centro, utilizando o valor de precisão (accuracy) como raio, representando a área aproximada onde o usuário pode estar.

Já no evento locationerror, exibimos apenas uma mensagem de alert informando que não foi possível obter a localização.

Essa foi uma breve introdução ao Leaflet, uma opção bastante interessante para quem precisa de uma biblioteca open-source para exibição de mapas interativos no navegador.

Além disso, o Leaflet conta com um ecossistema rico de plugins, que permite expandir suas funcionalidades para diversos cenários.

E por hoje é isso. T++