Posts com Tag ‘OpenLayers’

1. Por que da necessidade de um proxy?

Todos os navegadores modernos impõem uma restrição de segurança em conexões de rede, que inclui chamadas para XMLHttpRequest. Esta restrição impede que um script (ou aplicativo) de fazer uma conexão com qualquer servidor web que não seja o do próprio domínio de onde fora carregado (Internet Explorer permitirá solicitações entre domínios se a opção tiver sido ativada nas preferências). Se tanto a aplicação web e os dados XML que usa aplicativos vêm diretamente do mesmo domínio, então não haverá qualquer restrição e solicitações de dados podem ser realizadas livremente. Ou seja, quando um código JavaScript está carregando dados HTML, XML ou JSON de um recurso no mesmo domínio, tudo que é necessário é solicitar normalmente (de forma direta) estes dados. Observe a figura a seguir.

Solicitação de dados do mesmo servidor: sem necessidade de proxy.

Se, entretanto, a aplicação web é carregada de um determinado domínio web e esta aplicação faz solicitações de dados de serviços da Web de outro domínio, o navegador do usuário impedirá que a conexão seja aberta. Pode ser uma chatice a princípio, mas isto se justifica por questões de segurança. Observe a figura a seguir.

Solicitação de dados de outro host: necessidade de proxy.

Há uma série de soluções para esta situação, mas o mais comumente usado é a instalação de um proxy no próprio servidor web de onde a aplicação fora carregada. Em vez de fazer chamadas XMLHttpRequest diretamente para o serviço web, se faz as chamadas para o proxy do servidor web do domínio da aplicação. O proxy, em seguida, passa a chamada para o serviço web no host destino e, em contrapartida, passa os dados para o aplicativo cliente quando estes forem respondidos. Como a conexão é feita para o servidor da aplicação, e os dados de volta provêm do servidor da aplicação, o navegador não tem nada a reclamar. Observe a figura abaixo.

Solicitação de dados de outro servidor: proxy funcionando.

Conclusão: é necessário ter um web proxy funcionando para ultrapassar as limitações de segurança do Javascript quando se tem que requisitar dados de um servidor diferente daquele da aplicação.
OBS:
Mais rigorosamente, a origem de uma página é definida por seu protocolo, host e porta. Por exemplo, a origem da página deste post é (‘http’,’concani3.wordpress.com’, 80).

2. Um simples web proxy em Python para uso com a biblioteca OpenLayers
Vamos mostrar neste post a superação da restrição “cross-domain” através de um simples script com função Proxy, escrito em Python. Isto irá fazer com que o código JavaScript da aplicação possa acessar conteúdos de webpages de outros domínios, superando estas limitações de segurança “cross-domain”. É necessário para isto instalar este script no servidor de onde é carregado a aplicação JavaScript.

2.1 Disponibilizar o script que implementa o proxy
No caso da bilbioteca OpenLayers-2.12, esta traz uma sugestão de script em Python para funcionar como um proxy. Esse script é encontrado em OpenLayers-2.12/examples/proxy.cgi. Há a necessidade de alterar apenas uma instrução deste arquivo: incluir o nomes dos domínios destino (ou dos dominios destinos) no array “allowedHosts”. E nada mais. Por exemplo, se houvesse a necessidade de requisitar dados dos dominios http://www.exemplo1.com.br e de http://www.exemplo2.com.br, a configuração ficaria assim:
allowedHosts = ['www.exemplo1.com.br', 'www.exemplo2.com.br']

Vamos disponibilizar este arquivo numa pasta tradicionalmente comum para scripts cgi: em /usr/lib/cgi-bin. Assim, fazer:

# cp pasta_do_OpenLayers/OpenLayers-2.12/examples/proxy.cgi /usr/lib/cgi-bin/.

OBS:
– se a pasta destino cgi-bin não estiver criada, faça sua criação;
– por padrão, o interpretador Python em máquinas Linux já vem instalado. Portanto, nada a se preocupar quanto a este aspecto.

2.2 Código do web proxy
Por conveniência, vamos disponibilizar aqui este código do web proxy fornecido pela biblioteca OpenLayers:


#!/usr/bin/env python

import urllib2
import cgi
import sys, os

# Designed to prevent Open Proxy type stuff.

allowedHosts = ['www.openlayers.org', 'openlayers.org', 
                'labs.metacarta.com', 'world.freemap.in', 
                'prototype.openmnnd.org', 'geo.openplans.org',
                'sigma.openplans.org', 'demo.opengeo.org',
                'www.openstreetmap.org', 'sample.azavea.com',
                'v2.suite.opengeo.org', 'v-swe.uni-muenster.de:8080', 
                'vmap0.tiles.osgeo.org', 'www.openrouteservice.org',
                'maps.wien.gv.at','www.exemplo1.com.br','www.exemplo2.com.br']

method = os.environ["REQUEST_METHOD"]

if method == "POST":
    qs = os.environ["QUERY_STRING"]
    d = cgi.parse_qs(qs)
    if d.has_key("url"):
        url = d["url"][0]
    else:
        url = "http://www.openlayers.org"
else:
    fs = cgi.FieldStorage()
    url = fs.getvalue('url', "http://www.openlayers.org")

try:
    host = url.split("/")[2]
    if allowedHosts and not host in allowedHosts:
        print "Status: 502 Bad Gateway"
        print "Content-Type: text/plain"
        print
        print "This proxy does not allow you to access that location (%s)." % (host,)
        print
        print os.environ
  
    elif url.startswith("http://") or url.startswith("https://"):
    
        if method == "POST":
            length = int(os.environ["CONTENT_LENGTH"])
            headers = {"Content-Type": os.environ["CONTENT_TYPE"]}
            body = sys.stdin.read(length)
            r = urllib2.Request(url, body, headers)
            y = urllib2.urlopen(r)
        else:
            y = urllib2.urlopen(url)
        
        # print content type header
        i = y.info()
        if i.has_key("Content-Type"):
            print "Content-Type: %s" % (i["Content-Type"])
        else:
            print "Content-Type: text/plain"
        print
        
        print y.read()
        
        y.close()
    else:
        print "Content-Type: text/plain"
        print
        print "Illegal request."

except Exception, E:
    print "Status: 500 Unexpected Error"
    print "Content-Type: text/plain"
    print 
    print "Some unexpected error occurred. Error text was:", E

 
2.3 Configurar o Apache
Vamos fazer aqui uma configuração muito simples do Apache, sem lançar mão de criação de “Virtual Hosts”. Editar o host virtual default, criado quando da instalação do Apache, o 000-default:
Editar /etc/apache2/sites-enabled/000-default, alterando algumas de suas linhas:


    # Para poder executar a chamada proxy pela instrução:
    # OpenLayers.ProxyHost = "/cgi-bin/proxy.cgi?url=";
	ScriptAlias /cgi-bin/ /usr/lib/cgi-bin/
	<Directory "/usr/lib/cgi-bin">
		AllowOverride None
		Options +ExecCGI -MultiViews +SymLinksIfOwnerMatch
		Order allow,deny
		Allow from all
        </Directory>

 
Reiniciar o Apache:
# /etc/init.d/apache2 restart

2.4 Testar o funcionamento do web proxy
Para verificar o funcionamento do proxy, dois testes muito simples podem ser realizados.

a) Primeiro teste
Na barra de endereços do navegador, digitar o endereço do proxy passando como parâmetro um endereço que consta em “allowedHosts”. Por exemplo, o endereço openlayers.org. Fazer assim:
http://nome-servidor-do-proxy/cgi-bin/proxy.cgi?url=http://openlayers.org

Deve ser mostrado a página inicial do OpenLayers, solicitada através do proxy.

b) Segundo teste
Na barra de endereços do navegador, digitar o endereço do proxy passando como parâmetro um endereço que NÃO consta em “allowedHosts”. Por exemplo, o endereço uol.com.br. Fazer assim:
http://nome-servidor-do-proxy/cgi-bin/proxy.cgi?url=http://uol.com.br

Uma mensagem de negação do proxy deve ser mostrada na tela. Algo assim:


This proxy does not allow you to access that location (uol.com.br).

{'HTTP_COOKIE': '__utma=145174525.209261312.1401672948.1401672948.1401672948.1;
 __utmb=145174525.1.10.1401672948; __utmc=145174525; __utmz=145174525.1401672948.1.1.utmcsr=(direct)|utmccn=(direct)|utmcmd=(none)',
 'SERVER_SOFTWARE': 'Apache', 'SCRIPT_NAME': '/cgi-bin/proxy.cgi', 'SERVER_SIGNATURE': '', 'REQUEST_METHOD': 'GET',
 'SERVER_PROTOCOL': 'HTTP/1.1', 'QUERY_STRING': 'url=http://uol.com.br', 'PATH': '/usr/local/bin:/usr/bin:/bin',
 'HTTP_USER_AGENT': 'Mozilla/5.0 (X11; Linux x86_64; rv:17.0) Gecko/20131030 Firefox/17.0 Iceweasel/17.0.10',
 'HTTP_CONNECTION': 'keep-alive', 'SERVER_NAME': 'nome-servidor-do-proxy', 'REMOTE_ADDR': 'IP_do_navegador_usuario',
 'SERVER_PORT': '80', 'SERVER_ADDR': 'nome-servidor-do-proxy', 'DOCUMENT_ROOT': '/var/www',
 'SCRIPT_FILENAME': '/usr/lib/cgi-bin/proxy.cgi', 'SERVER_ADMIN': 'webmaster@localhost', 'HTTP_HOST': 'nome-servidor-do-proxy',
 'REQUEST_URI': '/cgi-bin/proxy.cgi?url=http://uol.com.br', 'HTTP_ACCEPT': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8',
 'GATEWAY_INTERFACE': 'CGI/1.1', 'REMOTE_PORT': '6315', 'HTTP_ACCEPT_LANGUAGE': 'pt-BR,pt;q=0.8,en-US;q=0.5,en;q=0.3',
 'HTTP_ACCEPT_ENCODING': 'gzip, deflate'
}

 
2.5 Definição do web proxy na aplicação
Estando o web proxy funcionando, na aplicação JavaScript que utiliza a bilbioteca OpenLayers basta inserir a definição do ProxyHost:

OpenLayers.ProxyHost = "/cgi-bin/proxy.cgi/?url=";

Referências:
1- JavaScript: use a Web Proxy for Cross-Domain XMLHttpRequest Calls
2- Same-origin policy
3- Cross-Domain requests in Javascript
4- Same-origin policy
5- In which ways could a javascript making a cross domain HEAD request be a threat?
6- W3C – Cross-Origin Resource Sharing

Anúncios

1. Um recorte de código
Veja o recorte de código abaixo:


    var saveStrategy = new OpenLayers.Strategy.Save();   
    wfs = new OpenLayers.Layer.Vector("Editable Features", {
        strategies: [new OpenLayers.Strategy.BBOX(), saveStrategy],
        projection: new OpenLayers.Projection("EPSG:4326"),
        protocol: new OpenLayers.Protocol.WFS({
            version: "1.1.0",
            srsName: "EPSG:4326",
            url: "endereço-servidor:8080/geoserver/wfs/",
            featureNS :  "endereço-servidor/catalogteste",
            featureType: "escolas",
            geometryName: "the_geom",
            schema: "endereço-servidor:8080/geoserver/wfs/DescribeFeatureType?version=1.1.0&typename=teste:escolas"
        })
    });    
    mapa.addLayer(wfs);

a)Onde:

  • featureNS : {String} Feature namespace (opcional).
  • featureType : {String} feature typeName local (sem prefixo) (obrigatório).
  • geometryName: {String} Opcional, nome do atributo geometry. Se não fornecido, será utilizado o nome default que é ‘the_geom’.

b)Como identificar os parâmetros?
Basta acessar o Geoserver como administrador e verificar os parâmetros dos layers criados:

  • featureNS: no Geoserver –> Edit Workspace –> Namespace URI. O que será colocado em featureNS deve ser igual ao que temos neste campo do Geoserver.
  • geometryName: não fizemos citação porque utilizamos o nome default. Para verificar isto, no Geoserver –> Edit Layer (no caso o layer escolas) –> Feature Type Details (que está no final da página). São encontradas aí o nome dos campos do layer. Para a geometria, vemos que o que está no geoserver é “the_geom”.

c) Para entender um pouco mais

  • Strategy Class: vide post OpenLayers: compreendendo melhor um “Layer Vetorial”. Em síntese, a classe Strategy é utilizada com dois objetivos: construir a requisição e determinar o que fazer com os dados.
    • Strategy.Save: uma estratégia que “commit” as features recém-criadas ou modificadas. Por padrão, antes de persistir as mudanças a “strategy” espera por uma chamada de “salvar”. Ao configurar a estratégia com a opção “auto”, as mudanças podem ser salvas automaticamente.
    • Função Save: diz ao “protocol” para realizar o “commit” das features não salvas. Se a projeção do layer difere da projeção do mapa, as features serão transformadas na projeção do layer antes do “commit”.
  • Protocol Class: vide post OpenLayers: compreendendo melhor um “Layer Vetorial”. Em síntese, a classe Protocolo se refere a comunicação, ou seja, controla como o layer vetorial se comunica com o servidor de dados. Existem 2 (duas) subclasses: Protocol.HTTP e Protocol.WFS.

Observação: Para carregar/salvar dados de alguma fonte de dados externa ao script, necessita-se no mínimo definir as classes Protocol e Strategy.
Referências:
1- OpenLayers: utilizando o protocolo WFS para requisitar features de servidor remoto Geoserver
2- OpenLayers: compreendendo melhor um “Layer Vetorial”
3- OpenLayers.Strategy.Save

O primeiro desafio é saber especificar os parâmetros exigidos pela classe OpenLayers.Protocol.WFS para se fazer a requisição. Dois parâmetros são essenciais:

  • featureNS : {String} Feature namespace (opcional).
  • featureType : {String} feature typeName local (sem prefixo) (obrigatório).

Parâmetros adicionais:

  • geometryName: {String} Opcional, nome do atributo geometry. Se não fornecido, será utilizado o nome default que é ‘the_geom’.
  • outputFormat:  {String} Opcional, formato de saída para uso do WFS GetFeature requests. Pode ser de qualquer formato presente na WFS’s GetCapabilities response.

No nosso caso, utilizaremos a seguinte chamada:

var wfs = new OpenLayers.Layer.Vector("States", {
strategies: [new OpenLayers.Strategy.BBOX()],
protocol: new OpenLayers.Protocol.WFS({
version: "1.1.0",
url: "HTTP://endereço-servidor:8080/geoserver/wfs",
featureType: "ra31_df",
featureNS: "http://endereço-servidor/catalogcdes",
srsName: "EPSG:4326"
})
});

Como identificar os parâmetros? Basta acessar o Geoserver como administrador e verificar os parâmetros dos layers criados:
a)  featureNS: no Geoserver –> Edit Workspace –> Namespace URI. O que será colocado em featureNS deve ser igual ao que temos neste campo do Geoserver.
b) geometryName: não fizemos citação porque utilizamos o nome default. Para verificar isto, no Geoserver –> Edit Layer (no caso o layer ra31_df) –> Feature Type Details (que está no final da página). São encontradas aí o nome dos campos do layer. Para a geometria, vemos que o que está no geoserver é “the_geom”.

<!DOCTYPE html> 
<html lang='pt'> 
<head> 
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <script type='text/javascript' src='http://localhost/js/OpenLayers-2.12/OpenLayers.js'></script>
    <title>WFS</title>
    <script type='text/javascript'>
    var map;
    const HOSTNAME = "HTTP://nome-servidor:8080/geoserver/wms";
    //
    function init() {
        map = new OpenLayers.Map('map', {
            controls: [
                new OpenLayers.Control.PanZoom(),
                new OpenLayers.Control.MousePosition({}),
                new OpenLayers.Control.LayerSwitcher(),
                new OpenLayers.Control.Navigation()
            ]
        });
    //WMS Layer - baseLayer
    layer = new OpenLayers.Layer.WMS(
    'DF:RIDE',
    HOSTNAME,
    {layers: 'cdes:ride_df', transparent:true},{attribution:'Ride',visibility: true, 
      isBaseLayer:true,opacity:1,displayInLayerSwitcher:true}
    );
    //
    map.addLayer(layer);
    //
    map.setCenter(new OpenLayers.LonLat(-47.85,-15.80),10);
//
 var wfs = new OpenLayers.Layer.Vector("RAs", {
        strategies: [new OpenLayers.Strategy.BBOX()],
        protocol: new OpenLayers.Protocol.WFS({
            version: "1.1.0",
            url: "HTTP://nome-servidor:8080/geoserver/wfs",
            featureType: "ra31_df",
            featureNS: "http://nome-servidor/catalogcdes",
            srsName: "EPSG:4326"
        })
 }); 
 map.addLayer(wfs);
//
    }
    </script>
  </head>
  <body onload="init()">
<h1 id="title">WFS: Protocol.WFS</h1>
<p id="shortdesc">
    Demostra como usar o protocol WFS para buscar features em um servidor remoto e colocar estas informações
 em um layer vetorial tornando-as visiveis em um mapa.
</p>
<div id="map" style='width: 800px; height: 400px;'></div>
</body>
</html>

Referências:
OpenLayers: utilizando o protocolo WFS para salvar features no servidor remoto Geoserver

1. Introdução
Eventos são o coração do JavaScript. São impulsos que desencadeiam reações. Cada classe capaz de emitir eventos é responsável por gerenciar seus “listeners” (aquelas funções que devem ser notificadas quando um evento é disparado) e também por emitir eventos sob certas circunstâncias. Por exemplo, é possível disparar eventos para quando o zoom do mapa mudar, quando um layer for carregado ou quando uma feature for adicionada ao layer. Eventos tem o significado de “algo está acontecendo” e deve ter um tratamento.

Mas como funciona mesmo os eventos no OpenLayers?
Para explicar de uma maneira fácil, vamos exemplificar através do tipo de evento “zoomend” da classe OpenLayers.Map. Para este tipo de evento, todas as vezes que o mapa muda seu zoom, ele é reponsável por disparar (trigger) o evento do tipo “zoomend”, e daí todos seus listeners serão notificados deste novo evento.

Para ajudar em todos estes processos, o OpenLayers possui a classe OpenLayers.Event, que cuida dos “registering listeners” e simplifica a ação de acionamento do evento para todos estes listeners (obs: um evento pode acionar vários listeners). Simplificadamente, esta classe permite:

  • definir o evento;
  • registrar os listeners;
  • acionar os eventos para notificar-se todos os listeners.

Quando um evento acontece, o mecanismo de gerenciamento de eventos do OpenLayers sempre chama a função listener passando um parâmetro “event”. Este objeto passado contém as informações que foram escritas pela classe que disparou o evento, mais as seguintes 3 propriedades que são sempre adicionadas automaticamente:

  •  type: contém o tipo do evento (exemplos: move, zoomend, etc);
  • object: aponta para o objeto que disparou o evento;
  •  element: o elemento DOM relacionado ao evento.

A construção é a seguinte:

register: function ( type, obj, func_listener, priority )

2. Tipos de eventos e forma de utilização
Os eventos são o que conduz as interações no OpenLayers, que, como dito anteriormente, possui sua própria classe Openlayers.Event. No OpenLayers existem dois tipos de eventos utilizados:

  • Browser Events: por exemplo, clicking do mouse no mapa.
  • Map Events: por exemplo, zooming do mapa para alguma coordenada.

Neste post estaremos interessados em explorar este segundo tipo de evento, onde existem duas formas de se utilizar os “Map Events” (que ao final realizam a mesma coisa):

  • no instante da criação do mapa já se adicionar os eventos;
  • definir os eventos em um instante posterior àquele da criação do mapa.

a) no instante da criação do mapa já se adicionar os eventos.
Para isto utilizaremos a propriedade eventListeners, tanto na classe OpenLayers.Map como na classe OpenLayers.Layer. Esta é a forma apropriada de se utilizar um Map Event desde que já se saiba no instante da criação do objeto mapa quais os eventos que se deseja adicionar ao mapa.
Exemplo:


// Criar as funções que serão chamadas pelos eventListeners
function movimento_listener(e){alert(
                   'Tipo do evento= ' + e.type + 
                   '\nClasse que disparou o evento= ' + e.object.CLASS_NAME +
                   '\nElement DOM= ' + e.element);};
function layer_listener(e){alert(
                   'Tipo do evento= ' + e.type + 
                   '\nClasse que disparou o evento= ' + e.object.CLASS_NAME +
                   '\nElement DOM= ' + e.element);};                  
// Criar o objeto mapa informando a propriedade eventListener. Neste caso, dois listeners.
map = new OpenLayers.Map('map_id',{
      eventListeners:{
                      'moveend': movimento_listener,
                      'changelayer': layer_listener
}});

 
OBS:
i) A propriedade eventListener é estruturada através de par key:value (eventType:listenerFunction);
ii) Os objetos eventListeners serão registrados com a classe OpenLayers.Events.on.

b) definir os eventos em um instante posterior àquele da criação do mapa.
Para isto utilizaremos a propriedade “events”. Esta forma de se utilizar o Map Event tem a diferença em relação a primeira forma pelo fato que definimos o tipo de evento em um instante posterior àquele da criação do objeto mapa. Existem dois métodos construtivos, mas que proporcionam o mesmo resultado:

Método-1: utilizando o método “register”
Exemplo:


// As funções listeners são as mesmas do último exemplo acima. 
// Não foram replicadas aqui. 
// Adicionar os eventos ao mapa que fora criado anteriormente,
//  uma instrução por tipo de evento.
map_name.events.register('moveend',map_name,movimento_listener);
map_name.events.register('changelayer',map_name,layer_listener);

 

OBS: no campo da propriedade object poderiamos utilizar “null” ou “this”, e o OpenLayers assumiria o próprio escopo de execução da instância (“this”).

Método-2: utilizando o método “on”
É um conveniete método para registrar listeners dentro de um escopo comum. Internamente, este método chama o método “register”.


Exemplo:
  // As funções listeners são as mesmas do último exemplo acima. Não copiadas aqui. 
  // Adicionar os eventos ao mapa que fora criado anteriormente, todos os eventos de uma única vez.
map_name.events.on({
	'moveend':movimento_listener,
	'changelayer':layer_listener
});

 

3. Map event Types
Temos falado em tipos de eventos mais ainda não expomos quais os tipos de eventos suportados. Na verdade cada classe que tem a propriedade “events” define um array com os tipos de eventos possíveis para aquela classe. Por exemplo, os tipos de eventos suportados pela classe OpenLayers.Map versão 2.12 são:
EVENT_TYPES: [“preaddlayer”, “addlayer”, “preremovelayer”, “removelayer”, “changelayer”, “movestart”, “move”, “moveend”, “zoomend”, “mouseover”, “mouseout”, “mousemove”, “changebaselayer”]

Já para a classe OpenLayers.Layer versão 2.12 temos:
EVENT_TYPES: [“loadstart”, “loadend”, “visibilitychanged”, “move”, “moveend”, “added”, “removed”]

Para a classe OpenLayers.Layer.Vector versão 2.12, além dos tipos suportados para a classe OpenLayers.Layer, adicionalmente também suporta:
EVENT_TYPES: [“beforefeatureadded”, “beforefeaturesadded”, “featureadded”, “featuresadded”, “beforefeatureremoved”, “beforefeaturesremoved”, “featureremoved”, “featuresremoved”, “beforefeatureselected”, “featureselected”, “featureunselected”, “beforefeaturemodified”, “featuremodified”, “afterfeaturemodified”, “vertexmodified”, “vertexremoved”, “sketchstarted”, “sketchmodified”, “sketchcomplete”, “refresh”]

Uma forma outra forma prática de ver os eventos suportados pelas classes do OpenLayers é utilizando o Firebug executando comandos através de seu console:
– para a classe OpenLayers.Map: “map.events.listeners”;
– para a classe Openlayers.Layer: “nome_layer.events.BROWSER_EVENTS” (neste caso encontra-se os tipos de eventos “Browser Events”).
Obs: vale salientar que este array de tipos de eventos suportados pode mudar a cada versão de OpenLayers, o que trará impacto sobre os programas desenvolvidos.

Referências:
1- OpenLayers: API Documentation
2- OpenLayers Library Documentation
Veja Também:
3- Um pouco de OpenLayers, Geoserver e PostGis

1. Introdução
Podemos afirmar que a feature é a centralidade da biblioteca OpenLayers. Ou de outra forma, a feature é o elemento central quando estamos trabalhando com GIS no client-side. O que se deseja, em última instância sob o ponto de vista client-side, é mostrar pontos, retas e polígonos no mapa e interagir com estes objetos geométricos. Ou seja, é trabalhar com as features.

Forma-se uma estrutura hierárquica: no mais baixo nível temos a geometria (o ponto, a reta, o polígono); quando a esta geometria são agregados os estilos e atributos constitui-se o objeto feature; a feature deverá ser incluída em um layer vetorial e, por último, esse layer será incluído no mapa. Com isto a feature ficará visível no mapa.

O processo de inclusão da feature no layer vetorial vem acompanhada de regras e filtros, possibilitando a este layer “impor” regras e não aceitar a inclusão de qualquer feature. Adicionalmente, essa feature pode ser manipulada por um conjunto de facilidades de controle (ou interação). É sobre esta dinâmica que trataremos neste post.

2. Do objeto geométrico ao mapa
2.1 A Geometria
Começando, a geometria é o nível mais baixo da cadeia. A geometria é um objeto vetorial, representada por uma classe que faz a descrição do objego geográfico. Esta classe, OpenLayers.Geometry, é responsável por armazenar a informação geográfica, além de ser o fundamento das features (como veremos mais adiante neste post).

A classe básica da geometria é a “OpenLayers.Geometry” (que chamaremos genericamente por “Geometry Class”). Mas existem diversas subclasses:
OpenLayers.Geometry.Point
OpenLayers.Geometry.LinearRing
OpenLayers.Geometry.LineString
OpenLayers.Geometry.MultiLineString
OpenLayers.Geometry.Polygon
e outras.

A “Geometry Class” possui diversos métodos, conforme pode ser visto no manual do OpenLayers. Veja a utilização prática de dois destes métodos:
a) Método distanceTo
O método distanceTo trabalha em medidas planares o qual assume o mundo como um plano usando coordenadas cartesianas.
A resposta será dada em graus. Para transformar o resultado para km: km = dist * 60 * 1.8520
Veja o exemplo:
var dist = geom_point1.distanceTo(geom_point2);

b) Método clone
Como o próprio nome já sugere, utilizada para clonar geometrias. Por exemplo:
var geom_point3 = geom_point1.clone();

2.2 A constituição de uma feature
Visto a geometria, chegamos ao conceito de feature. A idéia que está por traz do conceito de uma feature é a de representação dos elementos do mundo real através do GIS. Uma feature pode ser uma cidade, uma rodovia, uma estrada de ferro, uma região, um lago, a fronteira de um país e muito mais.

Cada feature é representada visualmente através de um símbolo geométrico: ponto, linha, polígono, um ícone, etc; possui um conjunto de atributos: população, tamanho e assim por diante; e usa algum estilo visual: cor, raio, largura, etc. Ou seja, cada feature possui geometria + atributos + style. A feature class está definida através da classe OpenLayers.Feature.Vector (como dito, a feature é um objeto vetorial), onde tem propriedades obrigatórias e opcionais:

Propriedade Descrição
geometry objeto geometry: é obrigatório, composto pela classe OpenLayers.Geometry.
atributes objeto attributes: opcional, contendo arbitrárias e serializadas propriedades que descrevem a feature
style objeto style: opcional. Não sendo definido, o OpenLayers utiliza seu estilo default.

Estrutura de criação (instanciação) de uma feature:
var nome_feature = new OpenLayers.Feature.Vector (geometry_object, {atributo-1_object,…,atributo-n_object},style_object);

As “features” (novamente: que são objetos vetoriais) devem pertencer a algum layer vetorial para poderem ficar visiveis no mapa. Ou seja, as features precisam ser adicionadas a algum layer vetorial. Assim, lembramos outra vez o conceito da estrutura hierárquica: “vector class” faz uso da “feature class” para possibilitar a visualização da feature no mapa. Por sua vez, para a “feature class” funcionar é necessário a utilização da “geometry class”.

Quanto a subclass: só existe uma única subclass da classe Feature: Feature.Vector

2.3 Incluir a feature no Mapa
Para colocar um objeto feature em um layer vetorial, faz-se uso da função pertencente a classe OpenLayers.Layer.Vector entitulada “addFeatures“:
addFeatures(Array{OpenLayers.Feature.Vector},option_object);
Onde:
– Array{OpenLayers.Feature.Vector}: lista de features para serem adicionadas.
– option_object: propriedades opcionais para alterar o comportamento da inclusão da feature no layer vetorial.

Exemplos:
map.layers[1].addFeatures ([nome_feature-1,….,nome_feature-n]);
layer_vetorial.addFeatures([feature-1,feature-2,feature-3]);

2.4 Uma demonstração prática desta sequência
Como dito, devem ser cumpridas 3 etapas para se ter o objeto geométrico representado no mapa: criar a geometria do ponto, associar esta geometria a uma feature e, por fim, adicionar a feature ao mapa. Vejamos estas etapas em exemplo prático:

a) Criar objetos geométricos
var geom_point1 = new OpenLayers.Geometry.Point(-53,-17);
var geom_point2 = new OpenLayers.Geometry.Point(-55,-15);
var geom_line1 = new OpenLayers.Geometry.LineString([geom_point1,geom_point2]);

b) Criar Features
var feature_point1 = new OpenLayers.Feature.Vector(geom_point1,{‘localizacao’:’lago Paranoá’,’descricao’:’muita água, muito bonito!’});
var feature_point2 = new OpenLayers.Feature.Vector(geom_point2);
var feature_line1 = new OpenLayers.Feature.Vector(geom_line1);

c) Adicionar Features ao Layer Vetorial, e tudo estar visivel no mapa
var layer_vetorial = new OpenLayers.Layer.Vector(‘Layer Testes Geometria’);
layer_vetorial.addFeatures([feature_point1,feature_point2,feature_line1]);
map.addLayer(layer_vetorial);

3. Interatividade
Para fazer alguma coisa acontecer quando do clicking do mouse sobre uma feature, há necessidade de se usar a classe selectFeature control: OpenLayers.Control.SelectFeature.

A classe de controle (ou de interatividade, como se queira) OpenLayers.Control.SelectFeature está associada a um determinado layer vetorial, e destina-se a selecionar objetos features deste layer quando do click do mouse ou o hover. Para se criar (instanciar) um objeto SelectFeature control, deve-se fazer:

var select_feature = new OpenLayers.Control.SelectFeature(vector_layer,{});
obs: vê-se que a classe está associada a um layer vetorial, no caso “vector_layer”. O espaço entre chaves deve ser preenchido com as propriedades desta classe (mas é opcional!).
Na forma como está, a feature já será selecionada no mapa e destacada das demais com um style de destaque default do OpenLayers. Nos exemplos abaixo mostraremos como melhorar o tratamento da(s) feature(s) destacada(s), através de comportamentos mais rebuscados ou com funções listeners de tratamento.

Algumas propriedades da classe SelectFeature control (consultar o manual para visão completa):

Propriedade Descrição
box {Boolean} Permite a seleção de features através do desenho de uma caixa.
clickout {Boolean} Deseleciona as features quando do clicking fora de qualquer feature.
events Classe {OpenLayers.Events} Instancia de eventos para listeners e controle de triggering dos eventos.
hover {Boolean} Seleciona quando o mouse estiver sobre a fearture e deseleciona quando o mouse sair da feature.
multiple {Boolean} Permite a seleção de múltiplas geometrias.
togle {Boolean} Deseleciona uma feature selecionada quando do click.
onSelect / onUnselect {Function} Função opcional para ser chamada quando uma feature for selecionada / deselecionada.
…. e outras

Também existem métodos sobre a classe SelectFeature control: como esta é uma classe de controle, este controle pode ser ativado ou desativado por funções em tempo de execução:

  • Activate – ativa o controle
  • Deactivate – desativa o controle

Exemplo:

var select_feature = new OpenLayers.Control.SelectFeature(
        layer_vetorial, 
        {
               multiple: false,
               toggle: true,
               toggleKey: 'ctrlKey',
               multipleKey: 'shiftKey' 
        }
);
map.addControl(select_feature); 
select_feature.activate();  // ativar o controle

Pelo exemplo acima, vemos a criação de um objeto de manipulação das features que pertencem ao  “layer_vetorial”, com 4 propriedades de funcionamento. Este objeto de interatividade (ou controle, como se queira) fora adicionado ao mapa e ativado. Ou seja, está em funcionamento.

3.1 Eventos
A classe SelectFeature control possui entre as suas propriedades a classe evento, como visto acima. Veja a seguir um exemplo de como utilizar esta propriedade:

		  	// Criar um layer vetorial
var layer_vetorial = new OpenLayers.Layer.Vector('Layer Testes Geometria');
var select_fc = new OpenLayers.Control.SelectFeature( 
       layer_vetorial,	// Adicionar interatividade com as features do layer vetorial
       {
            multiple: false,
            toggle: true,
            toggleKey: 'ctrlKey',
            multipleKey: 'shiftKey' 
        }
);
			// Criar as funções "listeners"
function selected_feature(event)  { alert("Feature selecionada",  feature.id);};
function unselected_feature(event){ alert("Feature deselecionada",feature.id);};
			// Registrar os eventos pertencentes as features do layer_vetorial
layer_vetorial.events.register('featureselected', this, selected_feature);
layer_vetorial.events.register('featureunselected', this, unselected_feature);
			// Adicionar o controle da interatiidade ao mapa e ativá-lo
map.addControl(select_fc);
select_fc.activate();

O que vemos? Sobre o layer vetorial adicionou-se interatividade (selecionar/deselecionar features), e criou-se listeners para tratamentos adicionais.

4. Firebug
O Firebug pode ser uma ferramenta essencial para fazer o debug dos scripts Javascript desenvolvidos, pois facilita o acesso as informações contidas em um Layer Vetorial e disponibiliza comandos. Utilizar o Firebug é muito fácil, pois é só usar as mesmas instruções do script desenvolvido, conforme se pode ver pelos exemplos abaixo:
map.layers
map.layers[3]
map.layers[1].features
map.layers[1].features[3]
map.layers[1].features[2].geometry
map.layers[1].features[2].attributes
map.layers[1].getFeatureByID(‘OpenLayers.Feature.Vector_342′)
map.layers[1].addfeatures([feature_point1])
map.layers[1].features[0].destroy()
map.layers[1].destroyFeatures([map.layers[1].features[0]])

Veja também o post Debug de scripts com Firefox.

Referências:
1- OpenLayers: API Documentation
2- OpenLayers Library Documentation
Veja Também:
3- Um pouco de OpenLayers, Geoserver e PostGis
4- Um exemplo de script utilizando a classe OpenLayers.Control.SelectFeature

Nesta receita pretende-se demostrar como usar o controle GetFeature, utilizando Protocol.WFS, para selecionar features de um layer WMS.

A classe OpenLayers.Control.GetFeature realiza busca por features para a localização sobre o cursor do mouse. Pode ser configurado para agir com o click, hover ou caixas de arrasto. Usa a classe OpenLayers.Protocol (HTTP ou WFS), a qual suporta filtros espaciais para adquirir features do servidor e disparar eventos que notificam aplicações associadas a feature selecionada.


<!DOCTYPE html> 
<html lang='pt'> 
<head> 
    <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
    <!-- Indicação da localização da bilbioteca OpenLayers -->
    <script type='text/javascript' src='./js/OpenLayers-2.12/OpenLayers.js'></script>
    <title>WFS: exemplo GetFeature (GeoServer)</title>
    <script type='text/javascript'>
    //    var map, layer, select, hover, control;
    var map, layer, select, control;
    const HOSTNAME = "HTTP://endereco_servidor:8080/geoserver/wfs";
    //
    function init() {
        map = new OpenLayers.Map('map', {
            controls: [
                new OpenLayers.Control.PanZoom(),
                new OpenLayers.Control.MousePosition({}),
                new OpenLayers.Control.LayerSwitcher(),
                new OpenLayers.Control.Navigation()
            ]
        });
    //WMS Layer: Ride-DF
    layer = new OpenLayers.Layer.WMS(
        'DF:Ride',
        HOSTNAME,
        {layers: 'cdes:ride_df', transparent:true},{attribution:'RIDE-DF',visibility: true, 
          isBaseLayer:true,opacity:.5,displayInLayerSwitcher:true}
    );
    //
    select = new OpenLayers.Layer.Vector("Selection", {styleMap: 
            new OpenLayers.Style(OpenLayers.Feature.Vector.style["select"])
    });
/*  Se desejar adicionar a facilidade hover, descomentar estas duas instruções e
    adicionar o layer hover ao mapa.
    Neste exemplo iremos deixar a opção "hover" desabilitada.
    Observer que o "hover" habilitado provoca um adicional de pedidos GetFeature 
    para ser emitido ao servidor de mapas.
    hover = new OpenLayers.Layer.Vector("Hover");
    map.addLayers([layer, hover, select]);
*/
    map.addLayers([layer, select]);
    control = new OpenLayers.Control.GetFeature({
        protocol: OpenLayers.Protocol.WFS.fromWMSLayer(layer),
        box: true,  // permite selecao de feature desenhando uma caixa
        hover: false,  // Se true, envia um pedido de feature quando mouse over.
        multipleKey: "shiftKey",
        toggleKey: "ctrlKey"
    });
    control.events.register("featureselected", this, function(e) {
        select.addFeatures([e.feature]);
    });
    control.events.register("featureunselected", this, function(e) {
        select.removeFeatures([e.feature]);
    });
/*
    Só necessário de facilidade hover habilitada
    control.events.register("hoverfeature", this, function(e) {
        hover.addFeatures([e.feature]);
    });
    control.events.register("outfeature", this, function(e) {
        hover.removeFeatures([e.feature]);
    });
*/
    map.addControl(control);
    control.activate();
    //
    map.setCenter(new OpenLayers.LonLat(-47.85,-15.80),8);
    }
    </script>
  </head>
  <body onload="init()">
<h1 id="title">WFS: exemplo de GetFeature (GeoServer)</h1>
<p id="shortdesc">
    Demostra como usar o controle GetFeature para selecionar features de um layer WMS.
</p>
<div id="map" style='width: 800px; height: 400px;'></div>
<div id="docs">
    <p>
        "Click" ou "drag" uma caixa para selecionar features, use a tecla "Shift" para adicionar
        features a seleção, use a tecla "Ctrl" para chavear o status da feature.
    </p>
</div>
</body>
</html>

Referências:
1- OpenLayers: API Documentation
2- OpenLayers Library Documentation
Veja Também:
3- Um pouco de OpenLayers, Geoserver e PostGis

Vamos construir um exemplo mostrando como selecionar uma feature e tratar eventos do click do mouse para mostrar os atributos da feature. Utilizaremos de “Control.SelectFeature” e . Vamos criar pontos, linhas e poligonos, colá-los no mapa, e usar o controle SelectFeature e tratadores de eventos para permitir ao usuário selecionar a feature e visualizar seus atributos.

Executando o script, basta clicar em cada feature para selecioná-la individualmente, ou <shift>+click para selecionar um grupo de features.


<!DOCTYPE html> 
<html lang='pt'> 
<head> 
    <meta charset='utf-8' /> 
    <!-- Indicação da localização da bilbioteca OpenLayers -->
    <script type='text/javascript' src='./js/OpenLayers-2.12/OpenLayers.js'></script>
    <script type='text/javascript'>
    var map;
    function init() {
    //
    //Criar o objeto mapa
    map = new OpenLayers.Map('map_id');
    //
    //Criar l layer básico raster e adicioná-lo ao mapa
    // Este mapa trabalha com a projeção geográfica (EPSG:4326)
    // que será utilizada diretamente ao longo deste exemplo
    var wms_layer = new OpenLayers.Layer.WMS(
            'OpenLayers WMS',
            'http://vmap0.tiles.osgeo.org/wms/vmap0',
            {layers: 'basic'},
            {}
    );
    map.addLayer(wms_layer);
    //
    // Adicionar dois controles ao mapa para auxiliar na visualização da localização
    map.addControl(new OpenLayers.Control.LayerSwitcher({title: 'Menu de layers disponíveis'}));
    map.addControl(new OpenLayers.Control.MousePosition({}));
    //
    // Fixar o centro do mapa no Distrito Federal, onde está a capital do Brasil
    // utilizaremos um nível de zoom=11 que dará uma boa aproximação para os propósitos deste exemplo
    map.setCenter(new OpenLayers.LonLat(-47.85,-15.80),11);
    if(!map.getCenter()){
            map.zoomToMaxExtent();
    }
    //
    // Criar um layer vetorial para sobrepor o layer raster e adicioná-lo ao mapa
    var layer_vetorial = new OpenLayers.Layer.Vector('Layer Testes Geometria');
    map.addLayer(layer_vetorial);
    //
    /* -------------------------------------------------------------------------- */
    // Vamos trabalhar um pouco criando geometrias, depois
    // criar as features e adicioná-las ao layer vetorial. Usaremos:
    // Geometry.Point, Geometry.LineString, Geometry.LinearRing e Geometry.Polygon
    var geom_point1 = new OpenLayers.Geometry.Point(-47.86,-15.83);
    var geom_point2 = new OpenLayers.Geometry.Point(-47.886,-15.741);
    var geom_point3 = new OpenLayers.Geometry.Point(-47.811,-15.748);
    var geom_line1  = new OpenLayers.Geometry.LineString(
                      [geom_point1,geom_point2]);
    var geom_line2  = new OpenLayers.Geometry.LineString(
                      [geom_point2,geom_point3]);
    //
    var feature_point1 = new OpenLayers.Feature.Vector(geom_point1,null);
    var feature_point2 = new OpenLayers.Feature.Vector(geom_point2,
                         {'localizacao':'lago Paranoá','descricao':'muita água, muito bonito!'});
    var feature_point3 = new OpenLayers.Feature.Vector(geom_point3);
    var feature_line1  = new OpenLayers.Feature.Vector(geom_line1,
                         {'localizacao':'linha norte-sul','descricao':'principal linha de onibus do centro de Brasilia.'});
    var feature_line2  = new OpenLayers.Feature.Vector(geom_line2,
                         {'localizacao':'bairro central de Brasilia-DF','descricao':'linha integração gratuita.'});
    //
    var feature_polygon = new OpenLayers.Feature.Vector(
        //Construir um polígono de "linear ring objects", os quais são pontos
        new OpenLayers.Geometry.Polygon(new OpenLayers.Geometry.LinearRing(
                [
                    new OpenLayers.Geometry.Point(-47.982,-15.734),
                    new OpenLayers.Geometry.Point(-47.913,-15.737),
                    new OpenLayers.Geometry.Point(-47.910,-15.795),
                    new OpenLayers.Geometry.Point(-47.976,-15.799)
                    //Não há necessidade de fechar o último ponto, ele será fechado automaticamente
                ]
        )),
        {'localizacao': 'Parque da Cidade', 'descricao': 'Excelente para lazer'}
    );
    //
    var feature_linestring = new OpenLayers.Feature.Vector(
            //Construir LinearRing
            new OpenLayers.Geometry.LinearRing(
               [
                   // Não há necessidade de especificar o último ponto.
                   // O próprio OpneLayers fecha o anel.
                   new OpenLayers.Geometry.Point(-47.886,-15.721),
                   new OpenLayers.Geometry.Point(-47.841,-15.625),
                   new OpenLayers.Geometry.Point(-47.811,-15.727)
               ]
            ),
            {'localizacao': 'Parque da Nacente de Água', 'descricao': 'um museu a céu aberto da natureza.'}
     );
    //
    layer_vetorial.addFeatures([feature_point1,feature_point2,feature_point3,
                              feature_line1,feature_line2,feature_polygon,feature_linestring]);
    /* -------------------------------------------------------------------------- */
    // Criar e adicionar o controle "selectFeature control"
    // e adicioná-lo ao mapa
    var select_feature_control = new OpenLayers.Control.SelectFeature(
                layer_vetorial, 
                {
                        multiple: false,
                        toggle: true,
                        toggleKey: 'ctrlKey',
                        multipleKey: 'shiftKey' 
                }
    );
    map.addControl(select_feature_control); 
    //Ativar o controle
    select_feature_control.activate();
    //
    // Finalmente, adicionar tratadores de eventos do mouse do usuário
    //que interage com as features
    function selected_feature(event){
            // limpar o conteúdo de log's
            document.getElementById('map_feature_log').innerHTML = '';
            //
            // Mostrar a feature corrente selecionada (passada pelo objeto evento)
            var mostrar_texto = 'Mouse clicou em: ' 
            + '<strong>' + event.feature.attributes.localizacao + '</strong>'
            + ': ' + event.feature.attributes.descricao + '<hr />';
           document.getElementById('map_feature_log').innerHTML = mostrar_texto;
           //
           //mostrar todas as features selecionadas
           document.getElementById('map_feature_log').innerHTML += 'Todas as features seleciondas: ';
          //
          //loop através do array de features selecionadas
          for(var i=0; i < layer_vetorial.selectedFeatures.length; i++){
            document.getElementById('map_feature_log').innerHTML += 
                layer_vetorial.selectedFeatures[i].attributes.localizacao + ' | ';
          }
    }
    //
    function unselected_feature(event){
          var mostrar_texto = event.feature.attributes.localizacao + ' não selecionado!' + '<hr />';
          document.getElementById('map_feature_log').innerHTML = mostrar_texto;
          //
          // Mostrar todas features selecionadas
          document.getElementById('map_feature_log').innerHTML += 'Todas as features seleciondas: ';
          //
          // Loop através do array de features selecionadas
          for(var i=0; i < layer_vetorial.selectedFeatures.length; i++){
            document.getElementById('map_feature_log').innerHTML += 
               layer_vetorial.selectedFeatures[i].attributes.localizacao + ' | ';
          }
    }
    //
    // Register o evento
    layer_vetorial.events.register('featureselected', this, selected_feature);
    layer_vetorial.events.register('featureunselected', this, unselected_feature);
    }
    </script>
</head>
<body onload='init();'>
    <div id='map_id' style='width: 800px; height: 400px;'></div>
    <div id='map_feature_log'></div>
</body>
</html>

Referências:
1- OpenLayers: API Documentation
2- OpenLayers Library Documentation
Veja Também:
3- Um pouco de OpenLayers, Geoserver e PostGis