• Início
  • Conhecimento básico
  • Ferramentas
  • Criando Mapas Interativos
  • Geoprocessamento
  • Módulo III - Mapas Interativos com Leaflet e Postgis.

    Parte 1 - O primeiro Mapa

    No modelo que criamos usando Go vamos adicionar um mapa base.

    Fazemos isso adicionando ao objeto mapa um objeto L.tileLayer que é uma base cartográfica que pode ser carregada dinamicamente.

    Modifique o arquivo mapa.html conforme abaixo:

    {{define "body"}}
      var map = L.map('mapa').setView([-3.09, -60.0], 12);
      var base = L.tileLayer('http://{s}.www.toolserver.org/tiles/bw-mapnik/{z}/{x}/{y}.png',{
      maxZoom: 19,
      attribution: '& copy;  <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'}).addTo(map);
    {{end}}
    

    E executamos:

    você@seu.computador:~/go/src/MapaLeaflet$ go install MapaLeaflet
    você@seu.computador:~/go/src/MapaLeaflet$ $GOPATH/bin/MapaLeaflet
    Servidor Rodando...
    

    Após iniciar o servidor, ao digitar localhost:8080/mapa.html no navegador web, teremos:

    Use o zoom + ou - para ver como o mapa interage.

    Com o objeto map 'map' e o objeto tileLayer 'base' temos o nosso primeiro mapa funcional.

    Na instrução var map = L.map('mapa').setView([-3.09, -60.0], 12); criamos o nosso objeto mapa no DOM de id 'mapa'. Este id foi definido no nosso template em um <div> e no <style> definimos sua área com 800x460 pixels onde o mapa será desenhado.

    O método setView deste objeto define que o mapa está centrado nas coordenadas fornecidas e o nível do zoom é 12.

    Existem várias opções e métodos para um objeto L.map, você pode checar a referência para este e os demais objetos neste link.

    Na instrução var base = L.tileLayer('http://{s}.www.toolserver.org/tiles/bw-mapnik/{z}/{x}/{y}.png',{ maxZoom: 19,attribution: '& copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'}).addTo(map); criamos uma base cartográfica de um provedor de 'tiles' (neste caso da OpenStreetMap) e definimos o zoom máximo em nível 19. A opção 'attribution' faz mostrar no canto inferior direito do mapa os créditos deste fornecedor de tiles.

    O Método addTo adiciona esta base ao objeto 'map'.

    Parte 2 - Integração Leaflet e Postgis

    Vamos agora mostrar como podemos interagir com o banco de dados. Primeiramente vamos carregar 3 objetos (arquivos shapefile) no nosso banco de dados PostgreSQL/postgis 'leaflet' que criamos no módulo anterior:

    Baixe os arquivos shapefile aqui:

  • pocos.shp - pocos.dbf - pocos.shx - pocos.prj - Dados ponto
  • 2dposc.shp - 2dposc.dbf - 2dposc.shx - 2dposc.prj - Dados linha
  • blocos.shp - blocos.dbf - blocos.shx - blocos.prj - Dados polígono

    Agora vamos carregar esses objetos geográficos no banco de dados 'leaflet' usando:
    você@seu.computador:~$ shp2pgsql -s 4326 -d pocos public.pocos | psql -d leaflet
    você@seu.computador:~$ shp2pgsql -s 4326 -d blocos public.blocos | psql -d leaflet
    você@seu.computador:~$ shp2pgsql -s 4326 -d 2dposc public.sismica | psql -d leaflet
    

    Temos agora 3 tabelas no banco de dados leaflet (pocos, blocos e sismica) prontos para serem usados por um mapa interativo.

  • O acesso a banco de dados pelo leaflet pode ser feito usando uma interface conhecida como AJAX (usando a linguagem PHP geralmente) ou podemos extrair diretamente um geoJSON do banco de dados via SQL chamado do programa Go e adicionar ele ao javaScript durante o carregamento da página.

    Vamos usar o segundo modo através do SQL abaixo que retorna um geoJSON da tabela 'nome' com os atributos listados em 'colunaX'

    SELECT row_to_json(fc) FROM ( 
    SELECT 'FeatureCollection' As type, array_to_json(array_agg(f)) As features FROM (
    SELECT 'Feature' As type,ST_AsGeoJSON(st_flipcoordinates(lg.geom))::json As geometry,
     row_to_json((coluna1,coluna2,...,colunaN)) 
    As properties FROM nome As lg) As f )  As fc;
    

    Vamos ver abaixo como criar o nosso mapa com os objetos geométricos do banco de dados.

    Parte 3 - Criando o Mapa com as Camadas extraídas do Banco de Dados

    Alteramos o arquivo layout.html para o mapa ocupar a tela inteira e poder ser visualizado melhor em aparelhos móveis.

    {{define "layout"}}
    <!DOCTYPE html>
    <html>
    <head>
     <title>Mapa Leaflet</title>
     <link rel="stylesheet" href="static/js/leaflet.css"/>
     <script  src="static/js/leaflet.js"></script>
     <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
     <style>
     #mapa {
      height: 600px;
      width: 1000px;
     }
     </style>
    </head>
    <body>
    <div id=mapa></div>
    <script>
      var grPocos= L.layerGroup();
      var grSismica= L.layerGroup();
      var grBlocos= L.layerGroup();
      var map = L.map('mapa',{center: [-3.7, -65],zoom: 6,layers:[grBlocos,grSismica,grPocos]});
      var base = L.tileLayer('http://{s}.www.toolserver.org/tiles/bw-mapnik/{z}/{x}/{y}.png',{maxZoom: 19,attribution: '© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'}).addTo(map);
      var imagem =  L.tileLayer('https://api.maptiler.com/maps/hybrid/{z}/{x}/{y}.jpg?key=krAZ650YRKtaBwGULajV',{tileSize: 512,zoomOffset: -1,minZoom: 1,attribution: '<a href="https://www.maptiler.com/copyright/" target="_blank">© MapTiler</a> <a href="https://www.openstreetmap.org/copyright" target="_blank">© OpenStreetMap contributors</a>',crossOrigin: true});
      var baseMaps = {"Base":base,"Satélite":imagem};
      var overlayMaps = {"Poços": grPocos,"Sísmica":grSismica,"Blocos": grBlocos};
      L.control.layers(baseMaps, overlayMaps).addTo(map);
      {{template "body"}}
    </script>
    </body>
    </html>
    {{end}}
    

    Deixamos vazio o mapa.html para carregar dinamicamente com o arquivo Go.

    {{define "body"}}
    
    {{end}}
    

    servidor.go:

    package main
    import (
      "strconv"
      "net/http"
      "database/sql"
      "text/template"
      "path/filepath"
      _ "github.com/lib/pq"
    )
    const sqlInfo = "host=localhost port=5432 user=user password=segredo dbname=leaflet sslmode=disable"
    //-----------------------------------------------------------------
    func jsonizador(camada string,atributos string,nome string) string{
      db, erro := sql.Open("postgres", sqlInfo)
      if erro != nil {
        panic(erro)
      }
      defer db.Close()
      sql := "SELECT row_to_json(fc) FROM ( SELECT 'FeatureCollection' As type, array_to_json(array_agg(f)) As features FROM (SELECT 'Feature' As type, ST_AsGeoJSON(st_flipcoordinates(lg.geom))::json As geometry, row_to_json(("+atributos+")) As properties FROM "+nome+" As lg) As f )  As fc;"
      jayzon := ""
      erro = db.QueryRow(sql).Scan(&jayzon)
      if erro != nil {
         panic(erro)
      }
      um := "let "+camada +" = new L.GeoJSON("
      return um + jayzon
    }
    //------------------------------------------------------------------
    func linEpoli(dados []string,colunas int) string{
      jayzon := jsonizador(dados[0],dados[10],dados[2])
      dois := `, {coordsToLatLng: function (coords) {return new L.LatLng(coords[0], coords[1], coords[2]);}, 
        onEachFeature: function(feature, marker) {`+
        "marker.setStyle({color:"+dados[3]+",fillCollor:" + dados[4] + ",weight:" + dados[5] + ",fillOpacity:" + dados[6] +",opacity:" + dados[7] +",radius:" + dados[8] +"});"+
        "marker.bindPopup('<table style=\"font-family: times, serif; font-size:9pt\" width=300><tr><th colspan=2>'+feature.properties.f1+'</th></tr>"
      tres :=" "
      for i:=1;i<=colunas;i++{
        tres = tres + " <tr><th>"+dados[10+i]+" </th><td>'+ feature.properties.f"+strconv.Itoa(i)+"+'</td></tr>"
      }
      quatro := "</table>');marker.bindTooltip('<b style=\"font-family: times, serif; font-size:9pt\">'+feature.properties.f1+'</b>');},stroke:"+dados[9]+"}).addTo(" + dados[1] + ");"+"\n"
      return jayzon + dois  + tres + quatro
    }
    //-------------------------------------------------------------------
    func ponto(dados []string,colunas int) string{
      jayzon := jsonizador(dados[0],dados[10],dados[2])
      dois := `, {coordsToLatLng: function (coords) {return new L.LatLng(coords[0], coords[1], coords[2]);}, 
        onEachFeature: function(feature, marker) {`+
        "marker.bindPopup('<table style=\"font-family: times, serif; font-size:9pt\" width=300><tr><th colspan=2>'+feature.properties.f1+'</th></tr>"
      tres :=" "
      for i:=1;i<=colunas;i++{
        tres = tres + " <tr><th>"+dados[10+i]+" </th><td>'+ feature.properties.f"+strconv.Itoa(i)+"+'</td></tr>"
      }
      quatro := "</table>');marker.bindTooltip('<b style=\"font-family: times, serif; font-size:9pt\">'+feature.properties.f1+'</b>');},pointToLayer: function (feature, latlng) {return L.circleMarker(latlng, {color: "+dados[3]+",opacity: " + dados[7] +"," +"weight: " + dados[5] + ",fillColor: " + dados[4] + ",fillOpacity: " + dados[6] +",radius: " + dados[8] + ",stroke:"+dados[9]+"});}}).addTo(" + dados[1] + ");"+"\n"
      return jayzon + dois  + tres + quatro
    }
    //---------------------------------------------------------------------
    func mapa(w http.ResponseWriter, r *http.Request){
      bloco :=[]string{"bloco","grBlocos","blocos","'#556B2F'","'#556B2F'","0.5","0.2","0.2","0", "true", "nom_bloco,nom_bacia,operador_c,num_descob,rodada","Bloco","Bacia","Operador","Número Descobrimentos","Rodada"}
      cam1 := linEpoli(bloco,5)
      sis :=[]string{"sis2d","grSismica","sismica","'#b40401'","'#b40401'","0.5","1.0","1.0","0","true","surv_name,ident","Nome do Levantamento","Identificação"}
      cam2 := linEpoli(sis,2)
      pocos :=[]string{"pocos","grPocos","pocos","'purple'","'cyan'","0.5","0.7","1.0","2.0", "true", "po_o_opera,bacia,bloco,campo,ativo,fase,operador,po_o_anp,objetivo,in_cio,t_rmino,conclus_o,profundida,sonda","Poço", "Bacia","Bloco","Campo","Ativo","Fase","Operador","Cod ANP","Objetivo","Início","Término","Conclusão", "Profundidade","Sonda"}
      cam3 := ponto(pocos,14)
      l := filepath.Join("templates", "layout.html")
      f := filepath.Join("templates", filepath.Clean(r.URL.Path))
      tpl, _ := template.ParseFiles(l, f)
      tudo :=cam1+cam2+cam3
      tpl.Parse("{{define \"body\"}}"+tudo+"{{end}}")
      tpl.ExecuteTemplate(w, "layout", nil)
    }
    //---------------------------------------------------------------------
    func main() {
      fs := http.FileServer(http.Dir("static"))
      http.Handle("/static/", http.StripPrefix("/static/", fs))
      http.HandleFunc("/mapa.html", mapa)
      println("Servidor Rodando...")
      http.ListenAndServe(":8080", nil)
    }
    

    Antes de executar você precisa carregar o seguinte drive para Go poder enxergar o banco de dados PostgreSQL:

    você@seu.computador:~/go/src/MapaLeaflet$ go get github.com/lib/pq
    

    Vamos agora ver o nosso mapa.

    Executando o servidor e acessando no navegador da mesma forma que anteriormente veremos o seguinte mapa:

    No canto superior esquerdo vemos o controle de camadas onde podemos selecionar a base e também as camadas que queremos ver:

    Ao clicar em um dos poços, ou nos outros elementos também, um popup aparecerá com informações extraídas do banco de dados:

    Explore o mapa um pouco mais para entender a dinâmica dele.

    Os métodos bindTooltip e bindPopup servem para mostrar informações sobre cada elemento de um objeto no Leaflet. Em nosso exemplo mostramos o primeiro atributo de cada geoJSON como 'Tooltip' e os dados dos atributos carregados como 'Popup'.

    O conteudo mostrado pelo dois pode ser em html e foi o que usamos nos dois casos. Para o Popup usei uma tabela.

    bindTooltip('<b style=\"font-family: times, serif; font-size:9pt\">'+feature.properties.f1+'</b>');
    
    bindPopup('<table style=\"font-family: times, serif; font-size:9pt\" width=300><tr><th colspan=2>'+feature.properties.f1+'</th></tr>
    <tr><th>"+dados[10+i]+" </th><td>'+ feature.properties.f2+'</td></tr>
    <tr><th>"+dados[10+i]+" </th><td>'+ feature.properties.f3+'</td></tr>
    ...
    </table>');
    

    O 'layerGroup' é usado para agrupar uma ou mais camadas em um determinado objeto, nesse exemplo criamos três 'layerGroups', um para cada geoJSON extraído do banco de dados. Isso foi feito para podermos incluir cada um deles no controle de camadas 'L.control.layers'.

    O 'L.control.layers' é usado para mostrar ou esconder os 'layerGroups' e também para selecionar qual 'tile' é visível, caso exista mais de um.

    Parte 4 - Plugins

    Um plugin é um objeto complementar para o leaflet geralmente desenvolvido por contribuidores externos para os usuários do leaflet.

    Baixe os arquivos desta página para o diretório js.

    Adicione as seguintes linhas no arquivo layout.html as duas primeiras no HEAD e a terceira logo após L.control.layers(baseMaps, overlayMaps).addTo(map);

    <link rel="stylesheet" href="static/js/leaflet.fullscreen.css"/>
    <script src="static/js/Leaflet.fullscreen.js"></script>
    
    map.addControl(new L.Control.Fullscreen());
    

    O mapa agora poderá ser visto no modo tela cheia:

    Outros plugins funcionam de forma similar.

    A base para criar mapas interativos foi aqui apresentada, agora depende só de você se aprofundar no assunto e desenvolver ótimos mapas.