Neste post iremos finalizar nosso artigo construindo uma aplicação para apresentar o nosso mapa de calor. Para isso você vai precisar para construir o aplicativo em algum lugar que seja acessível por um navegador web.

O mapa simples que podemos desenvolver usa apenas uma página com OpenLayers que mostra um mapa base e nossa camada WMS (geonames). Para isso você vai precisar de dois arquivos, basic-openlayers.html e basic-openlayers.js. No documento HTML coloque o seguinte:

<html>
  <head>
    <title>Basic Word Map</title>
    <script src="http://www.openlayers.org/api/2.12/OpenLayers.js"></script>
    <script src="basic-openlayers.js"></script>
  </head>
  <body onLoad="initMap()">
  <div id="map"></div>
  </body>
</html>
<html>
  <head>
    <title>Basic Word Map</title>
    <script src="http://www.openlayers.org/api/2.12/OpenLayers.js"></script>
    <script src="basic-openlayers.js"></script>
  </head>
  <body onLoad="initMap()">
  <div id="map"></div>
  </body>
</html>

O documento HTML apenas faz referência ao nosso arquivo javascript (OpenLayers) do aplicativo, e estabelece uma chamada “map” para realizar o vínculo com o OpenLayers. No documento JavaScript, coloque o seguinte código:

function initMap() {
  // Create the map
  var map = new OpenLayers.Map("map");
  // Create a WMS layer, with a base layer (states) and names layer (geonames)
  var wms = new OpenLayers.Layer.WMS("GeoServer WMS",
    "http://localhost:8080/geoserver/wms",
    {
      layers: "usa:states,opengeo:geonames"
    });
  // Set the word parameter for the geonames layer
  var myWord = "Navajo";
  wms.mergeNewParams({viewparams: "word:" + myWord});
  // Add the layer to the map and center the map on the USA
  map.addLayer(wms);
  map.setCenter(new OpenLayers.LonLat(-100, 38), 5);
}

O arquivo Javascript usa como base a camada “states” do GeoServer para servir como contexto para a camada “geonames”.

ol-navajo

Agora altere o valor da váriavel “myWord” e recarregue a página para ver os diferentes resultados no mapa.

Isto é muito interessante, mas seria ainda melhor se pudéssemos digitar uma palavra e imediatamente ver a mudança do mapa, sem precisar recarregá-la. Queremos também visualizar os resultados através de um mapa de calor, não apenas um mapa de pontos!

Para construir nossa aplicação final, usaremos o ExtJS, com a biblioteca GeoExt. Assim como acontece com os exemplos do OpenLayers, a página HTML do ExtJS é muito simples, pois é apenas um lugar para declarar as referências das bibliotecas que serão utilizadas.

<html>
  <head>
    <title>OpenGeo Word Map</title>
    <!-- ExtJS Scripts and Styles -->
    <script type="text/javascript" src="http://cdn.sencha.com/ext/gpl/3.4.1.1/adapter/ext/ext-base.js"></script>
    <script type="text/javascript" src="http://cdn.sencha.com/ext/gpl/3.4.1.1/ext-all.js"></script>
    <link rel="stylesheet" type="text/css" href="http://cdn.sencha.com/ext/gpl/3.4.1.1/resources/css/ext-all.css" />
    <link rel="stylesheet" type="text/css" href="http://cdn.sencha.com/ext/gpl/3.4.1.1/examples/shared/examples.css" />
    <!-- OpenLayers Script -->
    <script src="http://www.openlayers.org/api/2.12/OpenLayers.js"></script>
    <!-- GeoExt Script -->
    <script type="text/javascript" src="http://api.geoext.org/1.1/script/GeoExt.js"></script>
    <!-- Our Application -->
    <script type="text/javascript" src="wordmap-simple.js"></script>
  </head>
  <body>
  </body>
</html>

Você pode perceber que no arquivo HTML acima, são declaradas as bibliotecas ExtJS, GeoExt, OpenLayers e também o documento “wordmap-simple.js” que nada mais é que a nossa aplicação. Veja:

// Use this word on startup
var startWord = "Ocean";
// Base map
var osmLayer = new OpenLayers.Layer.OSM();
// Point map
var wmsLayer = new OpenLayers.Layer.WMS("WMS", "http://localhost:8080/geoserver/wms", {
  format: "image/png",
  transparent: true,
  layers: "opengeo:geonames",
  styles: "point"
}, {
  opacity: 0.6,
  singleTile: true,
});
// Start with map of startWord
wmsLayer.mergeNewParams({viewparams: "word:"+startWord});

Tal como acontece com o nosso exemplo simples, começamos definindo nossas camadas: o OpenStreetMap como mapa base; e a nossa camada WMS. Temos algumas opções extras na definição da camada WMS no OpenLayers, pois a definimos para ter um fundo transparente, e uma opacidade parcial, isso para sobrepor o mapa base.

Nós também definimos uma variável com nome de “startWord”, que irá iniciar o mapeamento da aplicação ao setá-la no parâmetro da URL.

Agora as coisas começam a ficam um pouco mais complicadas:

// Map with projection into (required when mixing base map with WMS)
olMap = new OpenLayers.Map({
  projection: "EPSG:900913",
  center: [-10764594.0, 4523072.0],
  units: "m",
  layers: [wmsLayer, osmLayer],
  zoom: 4
});

Como estamos misturando um mapa WMS e um mapa do OSM, nós precisamos:

1. Garantir que a camada WMS declare a projeção que está usando, que deve ser a Mercator para coincidir com o mapa base (OSM); e,
2. Garantir que o ponto central está convertido em Mercator antes de ser iniciado o mapa.

Agora que o mapa está configurado, só precisamos reunir os componentes ExtJS em uma aplicação:

// Text field component. On 'enter' update the WMS URL
var textField = new Ext.form.TextField({
  value: startWord,
  listeners: {
    specialkey: function(fld, e) {
      // Only update the map when the user hits 'enter'
      if (e.getKey() == e.ENTER) {
        wmsLayer.mergeNewParams({viewparams: "word:"+fld.getValue()});
      }
    }
  }
});

Para introduzir novas palavras precisamos de um campo de texto na tela. Quando o usuário clicar, ele deve pegar o evento e atualizar a camada (WMS).

// Map panel, with text field embedded in top toolbar
var mapPanel = new GeoExt.MapPanel({
  title: "OpenGeo Geonames Heat Map",
  tbar: ["Enter a word to map:", textField],
  map: olMap
});

Agora iremos adicionar um painel com o campo de texto embutido na barra de ferramentas no topo da tela:

// Viewport wraps map panel in full-screen handler
var viewPort = new Ext.Viewport({
  layout: "fit",
  items: [mapPanel]
});
// Start the app!
Ext.onReady(function () {
  viewPort.show();
});

Para finalizamos nossa aplicação vamos ver como ficou o nosso código em um único bloco:

// Use this word on startup
var startWord = "ocean";
// Base map
var osmLayer = new OpenLayers.Layer.OSM();
// Heat map + point map
var wmsLayer = new OpenLayers.Layer.WMS("WMS",
  // Uncomment below to use your local server
  // "http://localhost:8080/geoserver/wms",
  "http://apps.opengeo.org/geoserver/wms",
  {
    format: "image/png8",
    transparent: true,
    layers: "opengeo:geonames,opengeo:geonames",
    styles: "point,heatmap"
  }, {
    opacity: 0.6,
    singleTile: true,
  });
// Start with map of startWord
wmsLayer.mergeNewParams({viewparams: "word:"+startWord});
// Map with projection into (required when mixing base map with WMS)
olMap = new OpenLayers.Map({
  projection: "EPSG:900913",
  units: "m",
  layers: [wmsLayer, osmLayer],
  center: [-10764594.0, 4523072.0],
  zoom: 4
});
// Take in user input, fire an event when complete
var textField = new Ext.form.TextField({
  value: startWord,
  listeners: {
    specialkey: function(field, e) {
      // Only update the word map when user hits 'enter'
      if (e.getKey() == e.ENTER) {
        wmsLayer.mergeNewParams({viewparams: "word:"+field.getValue()});
      }
    }
  }
});
// Map panel, with text field embedded in top toolbar
var mapPanel = new GeoExt.MapPanel({
  title: "OpenGeo Geonames Heat Map",
  tbar: ["Enter a word to map:", textField],
  map: olMap
});
// Viewport wraps map panel in full-screen handler
var viewPort = new Ext.Viewport({
  layout: "fit",
  items: [mapPanel]
});
// Start the app!
Ext.onReady(function () {
  viewPort.show();
});

Veja agora como ficou o nosso mapa:

ext-ocean

Assim, ficou uma aplicação muito bonita, mas foi prometido um mapa de calor, e até agora, este é apenas um mapa de pontos! Nós precisamos adicionar um estilo de mapa de calor:

1. Clique em “Style” na barra de navegação do GeoServer
2. Selecione “Add new style”
3. Adicione o nome do estilo como “heatmap”
4. Cole o código abaixo na área de código

<?xml version="1.0" encoding="ISO-8859-1"?>
<StyledLayerDescriptor version="1.0.0"
   xsi:schemaLocation="http://www.opengis.net/sld StyledLayerDescriptor.xsd"
   xmlns="http://www.opengis.net/sld"
   xmlns:ogc="http://www.opengis.net/ogc"
   xmlns:xlink="http://www.w3.org/1999/xlink"
   xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
  <NamedLayer>
    <Name>Heatmap</Name>
    <UserStyle>
      <Title>Heatmap</Title>
      <Abstract>A heatmap surface</Abstract>
      <FeatureTypeStyle>
        <Transformation>
         <ogc:Function name="gs:Heatmap">
           <ogc:Function name="parameter">
             <ogc:Literal>data</ogc:Literal>
           </ogc:Function>
           <ogc:Function name="parameter">
             <ogc:Literal>radiusPixels</ogc:Literal>
             <ogc:Function name="env">
               <ogc:Literal>radius</ogc:Literal>
               <ogc:Literal>100</ogc:Literal>
             </ogc:Function>
           </ogc:Function>
           <ogc:Function name="parameter">
             <ogc:Literal>pixelsPerCell</ogc:Literal>
             <ogc:Literal>10</ogc:Literal>
           </ogc:Function>
           <ogc:Function name="parameter">
             <ogc:Literal>outputBBOX</ogc:Literal>
             <ogc:Function name="env">
               <ogc:Literal>wms_bbox</ogc:Literal>
             </ogc:Function>
           </ogc:Function>
           <ogc:Function name="parameter">
             <ogc:Literal>outputWidth</ogc:Literal>
             <ogc:Function name="env">
               <ogc:Literal>wms_width</ogc:Literal>
             </ogc:Function>
           </ogc:Function>
           <ogc:Function name="parameter">
             <ogc:Literal>outputHeight</ogc:Literal>
             <ogc:Function name="env">
               <ogc:Literal>wms_height</ogc:Literal>
             </ogc:Function>
           </ogc:Function>
         </ogc:Function>
        </Transformation>
        <Rule>
          <RasterSymbolizer>
          <!-- specify geometry attribute to pass validation -->
            <Geometry>
              <ogc:PropertyName>geom</ogc:PropertyName>
            </Geometry>
            <Opacity>0.6</Opacity>
            <ColorMap type="ramp" >
              <ColorMapEntry color="#FFFFFF" quantity="0"    label="nodata" opacity="0" />
              <ColorMapEntry color="#FFFFFF" quantity="0.02" label="nodata" opacity="0" />
              <ColorMapEntry color="#4444FF" quantity=".1"   label="nodata" />
              <ColorMapEntry color="#FF0000" quantity=".5"   label="values" />
              <ColorMapEntry color="#FFFF00" quantity="1.0"  label="values" />
            </ColorMap>
          </RasterSymbolizer>
        </Rule>
      </FeatureTypeStyle>
    </UserStyle>
  </NamedLayer>
</StyledLayerDescriptor>

Em seguida, clique no botão “Salvar”, e está pronto o nosso estilo heatmap! (Os parâmetros do estilo são explicados aqui).

Ao invocar a função gs:Heatmap no estilo, o GeoServer está utilizando o método heatmap disponível no serviço WPS (Web Process Service), que realiza o processamento e disponibiliza a camada através do WMS.

Para ativar o mapa de calor em nossa aplicação, nós só precisamos especificar esse estilo em nossa camada WMS. Então vamos fazer esta mudança na aplicação:

// Heat map + point map
var wmsLayer = new OpenLayers.Layer.WMS("WMS", "http://localhost:8080/geoserver/wms", {
  format: "image/png",
  transparent: true,
  layers: "opengeo:geonames,opengeo:geonames",
  styles: "point,heatmap"
}, {
  opacity: 0.6,
  singleTile: true,
});

Note que, em vez de substituir o nosso estilo de ponto, estamos adicionando um estilo heatmap a mesma camada (opengeo:GeoNames) uma segunda vez. Isso nos permite ver os pontos originais, bem como o mapa de calor.

ext-ocean-heat

Este tutorial é uma tradução e adaptação livre do artigo “Building a GeoNames Heat Map” publicado no site da Boundless.

Fonte: Boundless