Como gerar um webmap Leaflet.js no QGIS usando o plugin qgis2web, e ajustar o mapa gerado editando diretamente os códigos html e JavaScript
E mais: como adicionar uma visualização das coordenadas do ponteiro do mouse ao seu mapa do Leaflet
Colegas pesquisadores e entusiastas de SIG de código-livre,
Bem-vindos ao meu blog!
Gostaria de começar com um aviso - posso ser uma pesquisadora desta área, mas isso não significa que tudo o que faço ou escrevo aqui funcionará para você, em suas próprias configurações de desktop e versões de packages. Não tenho nenhuma responsabilidade se você perder dados ou estragar sua instalação. Eu também não autorizo nenhuma cópia do meu conteúdo.
Na semana passada, mostrei uma solução alternativa para evitar que o qgis2web gere uma pré-visualização quando você altera o tipo de mapa, travando o QGIS. Hoje, também falo sobre o qgis2web, mas sobre os ajustes que faço nos mapas para publicação online, após a exportação dos mesmos.
O plugin do QGIS qgis2web (atualmente, na sua versão 3.16) é usado para exportar seu projeto criado no QGIS como um webmap. Para fazer isso, você pode escolher entre os três tipos de webmap: OpenLayers, Leaflet e Mapbox GLJS. Todos eles geram mapas simples, mas bonitos, com estilo clean. Meu favorito é o Leaflet, também chamado de Leaflet.js, que é relativamente leve para carregar no navegador e tem uma documentação sólida.
Os mapas do Leaflet podem ser programados do zero. Muitas pessoas, e muitas empresas fazem isso. Mas, como sou entusiasta do QGIS e muitos dos meus mapas já estão em projetos do QGIS, usando o qgis2web, consigo ter o melhor dos dois mundos. O que eu faço é gerar meu mapa básico personalizando-o o tanto quanto possível no qgis2web e, em seguida, edito diretamente o html e o JavaScript gerado pelo plugin para ajustar o mapa às minhas necessidades. Neste post, vou mostrar como uso o qgis2web para gerar um webmap básico e o que olho e possivelmente altero após a exportação do mapa. Além disso, vou mostrar como implementar o código Leaflet.MousePosition, que serve para mostrar as coordenadas geográficas do local onde está o mouse/dedo (no caso do touch do celular).
Para ver uma versão simplificada do webmap que mostro como gerar neste post pode ser carregada, clique no botão abaixo. Aviso: este mapa pode levar algum tempo para carregar em conexões mais lentas.
Este mapa foi gerado pelo plugin qgis2web do QGIS, e posteriormente editado diretamente no código gerado. Habilidades avançadas de programação não são necessárias para se fazer estas modificações, embora elas se tornem mais fáceis se você tiver um conhecimento básico sobre desenvolvimento web.
Para as próximas etapas, usarei o QGIS 3.16 “Hannover” Desktop. Dados usados: cobertura da terra, rios, e lagos, de domínio público e disponíveis em Natural Earth e mapa de base do Google Maps © Google.
Primeiro: abra seu projeto no QGIS e ajuste a simbologia
Antes de exportar seu mapa, você deve ajustar a simbologia o tanto quanto possível, para que fique como deveria ser em seu mapa a ser publicado online. Lembre-se de que rasters grandes levam um certo tempo para exportar e podem não carregar corretamente em alguns navegadores. Mapas de base, no entanto, geralmente são carregados sob demanda diretamente no navegador do usuário. Algumas configurações, como estilos de rótulos (labels), podem não ser 100% transmitidos para o Leaflet.
Carreguei os dados e apliquei zoom em um local dentro do Brasil. Eu ajustei a simbologia dos lagos e dos rios e adicionei rótulos aos rios. Os nomes deles não tinham prefixo, então criei um novo campo na tabela de atributos para adicionar a palavra “Rio” antes dos nomes dos rios.
Abra a janela do qgis2web e selecione o tipo de mapa Leaflet
Se seus dados forem pesados, desmarque todas as camadas antes de alterar o tipo de mapa e não clique no botão de atualizar a visualização. Eu explico o porquê neste post.
Observe atentamente as opções e escolha as configurações apropriadas
Para o meu projeto, vou querer uma lista expandida de camadas, uma vez que a camada do Google Maps estará abaixo das outras camadas e não estará visível, a princípio. Ativei a busca por endereços, para que o usuário possa buscar por locais específicos.
Neste plugin, o tamanho do mapa gerado pode ser canvas-size (tamanho da tela) ou full-screen (toda a área disponível).
A configuração full-screen configurará o mapa para ocupar 100% da altura e largura disponíveis, enquanto a canvas-size fornecerá um mapa com tamanho fixo em pixels igual à sua visualização atual no QGIS. Mas não se preocupe, podemos alterar isso mais tarde com facilidade. Para este exemplo, gerarei um mapa com a configuração canvas-size.
Os níveis de zoom mínimo e máximo também são facilmente alteráveis diretamente no código html do mapa gerado. Definirei meu zoom máximo para o nível 15 e o mínimo para 2.
As outras configurações, não vou alterar neste momento.
Exporte o mapa.
Veja seu mapa no navegador
Até agora, você está satisfeito com seu mapa? Se sim, siga em frente.
Vejamos o código html gerado pelo qgis2web
Abra a pasta onde o mapa foi salvo. Se for uma pasta temporária, você pode querer movê-la para um local permanente no computador.
Dentro da pasta, você encontrará algo assim:
Abra o arquivo index.html em um editor. Eu estou usando o tradicional editor Vim. Observe a parte:
#map {
width: 824px;
height: 388px;
}
Esse é o template canvas-size. Altere a largura e a altura do mapa gerado conforme desejar. Exemplos:
#map {
width: 70%;
height: 60%;
}
#map {
width: 50em;
height: 50em;
}
É aqui que os links no canto inferior direito são gerados:
map.attributionControl.setPrefix('<a href="https://github.com/tomchadwin/qgis2web" target="_blank">qgis2web</a> · <a href="https://leafletjs.com" title="A JS library for interactive maps">Leaflet</a> · <a href="https://qgis.org">QGIS</a>')
Nesta parte, a camada do Google Maps é carregada:
map.createPane('pane_GoogleMaps_0');
map.getPane('pane_GoogleMaps_0').style.zIndex = 400;
var layer_GoogleMaps_0 = L.tileLayer('https://mt1.google.com/vt/lyrs=m&x={x}&y={y}&z={z}', {
pane: 'pane_GoogleMaps_0',
opacity: 1.0,
attribution: '',
minZoom: 2,
maxZoom: 15,
minNativeZoom: 0,
maxNativeZoom: 19
});
layer_GoogleMaps_0;
map.addLayer(layer_GoogleMaps_0)
Os valores minZoom e maxZoom foram preenchidos pelo qgis2web. Observe que o Google Maps é adicionado ao mapa com índice z de 400. Qualquer coisa com índice z superior a 400 será mostrada acima da camada do Google Maps e qualquer coisa com índice z inferior a 400 será mostrada abaixo da mesma.
Se você escolher alterar os níveis mínimo e máximo de zoom, lembre-se de alterá-los também em:
var map = L.map('map', {
zoomControl:true, maxZoom:15, minZoom:2
O layer raster é adicionado em:
map.createPane('pane_NE1_LR_LC_SR_W_DR_1');
map.getPane('pane_NE1_LR_LC_SR_W_DR_1').style.zIndex = 401;
var img_NE1_LR_LC_SR_W_DR_1 = 'data/NE1_LR_LC_SR_W_DR_1.png';
var img_bounds_NE1_LR_LC_SR_W_DR_1 = [[-89.99999999998198,-180.0],[90.0,179.99999999996396]];
var layer_NE1_LR_LC_SR_W_DR_1 = new L.imageOverlay(img_NE1_LR_LC_SR_W_DR_1,
img_bounds_NE1_LR_LC_SR_W_DR_1,
{pane: 'pane_NE1_LR_LC_SR_W_DR_1'});
bounds_group.addLayer(layer_NE1_LR_LC_SR_W_DR_1);
map.addLayer(layer_NE1_LR_LC_SR_W_DR_1);
Observe que o índice z desta camada é 401, o que significa que ela será mostrada acima do Google Maps.
Em seguida você tem a definição dos pop-ups, e, a seguir, esta parte, que é interessante:
function style_ne_10m_rivers_lake_centerlines_scale_rank_2_0() {
return {
pane: 'pane_ne_10m_rivers_lake_centerlines_scale_rank_2',
opacity: 1,
color: 'rgba(31,120,180,1.0)',
dashArray: '',
lineCap: 'square',
lineJoin: 'bevel',
weight: 2.0,
fillOpacity: 0,
interactive: true,
}
Nesta parte, muitos atributos da camada vetorial podem ser alterados, como cor e estilo de linha. Se eu rolar para baixo, encontrarei uma definição semelhante para a camada dos lagos:
function style_ne_10m_lakes_3_0() {
return {
pane: 'pane_ne_10m_lakes_3',
opacity: 1,
color: 'rgba(166,206,227,1.0)',
dashArray: '',
lineCap: 'butt',
lineJoin: 'miter',
weight: 1.0,
fill: true,
fillOpacity: 1,
fillColor: 'rgba(166,206,227,1.0)',
interactive: false,
}
}
Esta parte controla os rótulos (labels) da camada da rede de drenagem:
layer.bindTooltip((layer.feature.properties['name_rio'] !== null?String('<div style="color: #323232; font-size: 10pt; font-family: \'Arial\', sans-serif;">' + layer.feature.properties['name_rio']) + '</div>':''), {permanent: true, offset: [-0, -16], className: 'css_ne_10m_rivers_lake_centerlines_scale_rank_2'})
OK, agora que vimos o básico sobre este arquivo, podemos fazer alguns ajustes.
Ajuste: removendo um layer
Meu mapa tem muitos layers. Quero remover a camada raster, para tornar meu webmap mais leve para carregar no navegador.
Há dois lugares no código JavaScript onde devo fazer alterações:
- A definição da camada
Remova ou comente qualquer coisa de “map.createPane” para “map.addLayer”. Exemplo:
/*map.createPane('pane_NE1_LR_LC_SR_W_DR_1');
map.getPane('pane_NE1_LR_LC_SR_W_DR_1').style.zIndex = 401;
var img_NE1_LR_LC_SR_W_DR_1 = 'data/NE1_LR_LC_SR_W_DR_1.png';
var img_bounds_NE1_LR_LC_SR_W_DR_1 = [[-89.99999999998198,-180.0],[90.0,179.99999999996396]];
var layer_NE1_LR_LC_SR_W_DR_1 = new L.imageOverlay(img_NE1_LR_LC_SR_W_DR_1,
img_bounds_NE1_LR_LC_SR_W_DR_1,
{pane: 'pane_NE1_LR_LC_SR_W_DR_1'});*/
//bounds_group.addLayer(layer_NE1_LR_LC_SR_W_DR_1);
//map.addLayer(layer_NE1_LR_LC_SR_W_DR_1);
- Na lista de camadas
Na lista de camadas, você também deve remover o chamamento da camada. Exemplo:
// L.control.layers(baseMaps,{'<img src="legend/ne_10m_lakes_3.png" /> ne_10m_lakes': layer_ne_10m_lakes_3,'<img src="legend/ne_10m_rivers_lake_centerlines_scale_rank_2.png" /> ne_10m_rivers_lake_centerlines_scale_rank': layer_ne_10m_rivers_lake_centerlines_scale_rank_2,"NE1_LR_LC_SR_W_DR": layer_NE1_LR_LC_SR_W_DR_1,"Google Maps": layer_GoogleMaps_0,},{collapsed:false}).addTo(map);
L.control.layers(baseMaps,{'<img src="legend/ne_10m_lakes_3.png" /> ne_10m_lakes': layer_ne_10m_lakes_3,'<img src="legend/ne_10m_rivers_lake_centerlines_scale_rank_2.png" /> ne_10m_rivers_lake_centerlines_scale_rank': layer_ne_10m_rivers_lake_centerlines_scale_rank_2,"Google Maps": layer_GoogleMaps_0,},{collapsed:false}).addTo(map);
Alterar a ordem dos layers
Para alterar a ordem das camadas, reorganize o índice z de cada camada. Camadas com índices z mais baixos serão colocadas abaixo das outras.
Alterar o nome de uma camada
Você não precisa alterar o nome da camada em todos os locais do arquivo. Você pode simplesmente alterá-la no painel de layers.
No meu exemplo, eu substituí “layer_ne_10m_lakes_3” por simplesmente “lakes” (lagos, em inglês).
L.control.layers(baseMaps,{'<img src="legend/ne_10m_lakes_3.png" /> lakes': layer_ne_10m_lakes_3,'<img src="legend/ne_10m_rivers_lake_centerlines_scale_rank_2.png" /> ne_10m_rivers_lake_centerlines_scale_rank': layer_ne_10m_rivers_lake_centerlines_scale_rank_2,"Google Maps": layer_GoogleMaps_0,},{collapsed:false}).addTo(map);
A este ponto, o mapa está assim:
Alterar o tamanho da visualização do mapa
Para fazer isso, basta alterar a largura e a altura do mapa. Para obter melhores resultados em páginas responsivas (compatíveis com ambos dispositivos móveis e telas de computadores), escolha definir o tamanho como uma porcentagem da tela ou baseie a altura e a largura em uma unidade que varia com o tamanho da tela. Outra opção é envolver o webmap em um contêiner do tipo flex do Bootstrap JS.
Neste exemplo, eu coloquei
#map {
width: 98%;
height: 30em;
}
Como fica no computador:
Como fica na tela do celular:
Muito funcional!
Mude a cor e adicione transparência a um layer
Para mudar a cor e o alpha de uma camada, você deve alterar esta definição
color: 'rgba(31,120,180,0.5)',
Em que a cor é dada por RGB (intensidades de Vermelho, Verde e Azul) e o último valor é o alpha. Quando alpha vale 1, a camada está com a opacidade máxima (nada transparente), e quando alpha vale 0, a camada fica totalmente transparente. Eu escolhi 0.5, então, no caso, o layer fica com 50% de transparência.
Adicionar visualização de coordenadas ao passar o mouse
Você provavelmente percebeu que o mapa gerado não mostra as coordenadas geográficas do local sobre o qual o mouse está passando.
Eu gosto de ter esta informação mostrada em meus mapas, então pesquisei e encontrei esta implementação de Ardhi Lukianto chamada Leaflet.MousePosition. Ela está disponível sob licença do tipo MIT.
Observe que esta não é a única opção. No site do Leaflet há uma lista de implementações feitas por diferentes desenvolvedores, que também servem para mostrar as coordenadas do ponteiro do mouse.
Edit (12/07/21 21h): o Tiago Sousa do grupo QGISBrasil lembrou que ainda, é possível fazer você mesmo a implementação das coordenadas utilizando L.Control e um event listener de mousemove (movimento do mouse). Um exemplo está disponível nesta resposta do GIS StackExchange, mas, ao utilizá-lo, atentar a três mudanças importantes: 1) o último fecha-chaves está a mais; 2) o mapa gerado pelo qgis2web se chama simplesmente “map” em vez de “leafletMap”; 3) a expressão “LatLng” aparecerá antes das coordenadas, o que pode ser alterado simplesmente apagando essa expressão no código e deixando um espaço vazio entre as aspas.
Como implementar Leaflet.MousePosition em seu mapa:
Baixe o código-fonte do GitHub (ou dê um git clone)
Distribua os arquivos nas pastas certas
Para manter seu projeto organizado, você deve manter os arquivos CSS na pasta css, os JavaScript na pasta js, e assim por diante.
- Chame os arquivos em seu index.html
Na seção head do html, adicione:
<link rel="stylesheet" href="css/L.Control.MousePosition.css">
Na seção body do html, adicione o trecho:
<script src="js/L.Control.MousePosition.js"></script>
- Cole a seguinte linha no script (dentro de tags de script) em index.html
L.control.mousePosition().addTo(map);
- Creditando aos autores
Leaflet.MousePosition é distribuído sob a Licença MIT, o que significa que você precisa reconhecer o autor e o repositório.
Ao usar mapas base do Google, você também deve citar seu uso e os direitos autorais (copyright) do Google.
Você também deve citar o uso do plugin qgis2web.
Você não precisa me citar, mas eu certamente apreciaria se você fizesse isso, seja por um agradecimento, um link ou simplesmente contando para seus amigos sobre este site!
- Aproveite o seu mapa
Neste ponto, o mapa se parece com isto:
Você pode ver as coordenadas no canto inferior esquerdo. Verifique as opções na página do repositório para ver como você pode personalizar sua representação de coordenadas.
Observe que adicionei as referências ao repositório Leaflet.MousePosition e a este site no canto inferior direito. Fiz o mesmo no mapa de exemplo mostrado no início deste post.
Bons mapeamentos!
Extras
Por que os rótulos dos vetores ficam muito mais simples do que no QGIS?
Nem todos os aspectos do seu mapa no QGIS podem ser traduzidos para o webmap final, e os rótulos são um desses recursos que só podem ser parcialmente traduzidos pela versão atual do qgis2web.
Por que meu mapa está demorando tanto para carregar no navegador?
Você está trabalhando com big data? Rasters pesados tornam o carregamento do mapa especialmente mais lento.
Obtive o seguinte erro ao tentar exportar meu mapa no qgis2web:
“An error has occurred while executing Python code:
FileNotFoundError: [Errno 2] No such file or directory: ‘…Temp\\NE1_LR_LC_SR_W_DR11625779206_piped_3857.tif'
Traceback (most recent call last):
File "…\qgis2web\utils.py", line 424, in exportRaster
processing.run("gdal:translate", {"INPUT": piped_3857,
File "…\general.py", line 108, in run
return Processing.runAlgorithm(algOrName, parameters, onFinish, feedback, context)
File "…\Processing.py", line 168, in runAlgorithm
raise QgsProcessingException(msg)
_core.QgsProcessingException: Unable to execute algorithm
Could not load source layer for INPUT: …\..._piped_3857.tif not found
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "…\maindialog.py", line 348, in saveMap
results = writer.write(self.iface,
File "…\leafletWriter.py", line 90, in write
self.preview_file = self.writeLeaflet(
File "…\leafletWriter.py", line 202, in writeLeaflet
exportRaster(layer, lyrCount, layersFolder,
File "…\utils.py", line 444, in exportRaster
shutil.copyfile(piped_3857, out_raster)
File "…\shutil.py", line 264, in copyfile
with open(src, 'rb') as fsrc, open(dst, 'wb') as fdst:
FileNotFoundError: [Errno 2] No such file or directory: '…piped_3857.tif'
Python version: 3.9.5 (tags/v3.9.5:0a7dcbd, May 3 2021, 17:27:52) [MSC v.1928 64 bit (AMD64)]
QGIS version: 3.20.0-Odense Odense, decaadbb31”
Tive esse problema no QGIS 3.20.0 “Odense”, mas executei a exportação no QGIS 3.16 (a versão LTS) e funcionou. Eu sugiro que você tente o mesmo. O erro parece estar conectado à exportação do raster e à projeção usada no projeto do QGIS.