13 de novembro de 2012

PrimeFaces 3.4.1: GMap

Olá!
Depois de algum tempo, aqui estou eu com mais uma postagem sobre PrimeFaces, desta vez estou usando a versão 3.4.1. Peço desculpas se por acaso eu perdi a didática de escrever aqui, é que faz muito tempo que não escrevo mais.
Mas vamos lá... Hoje a postagem é sobre o GMap, pra quem ainda não sabe o PrimeFaces já tem um componente com a biblioteca GMap, pra usá-la é bem simples.
Primeiramente vamos baixar a biblioteca do Prime, basta clicar aqui.
Para este projeto estarei usando JPA, para quem ainda não sabe como usar: é só ver os primeiros links da página Nível: Intermediário.

Meu projeto ficou com essa estrutura:

Começando... adicione a biblioteca do PrimeFaces às bibliotecas do projeto. E na página index:

<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:p="http://primefaces.org/ui">
    <h:head>
        <script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=true" />
        <title>GMap</title>
    </h:head>
    <h:body>
        <p:gmap center="-15.565986,-54.309782" zoom="15" type="HYBRID" style="width:600px;height:400px" />  
    </h:body>
</html>

Vamos aos pontos importantes do código acima:
Linha 3: lembre-se de adicionar a taglib do prime na página
Linha 5: Ponto importante para o funcionamento do p:gmap, que é a biblioteca do Google para o maps funcionar.
Linha 9: Este código é suficiente para aparecer o mapa, e você já poderá navegar pelo mundo! Mas vamos aos detalhes dessa linha: a propriedade center se refere à latitude e longitude, esta propriedade indica qual será o ponto onde o mapa deverá abrir inicialmente; a propriedade zoom se refere ao zoom do mapa, que pelos meus testes pode ir de 0 até 20 (talvez em alguns lugares possa ir a mais ou menos que isso); a propriedade type trata da forma de visualizar o mapa, que pode ser HYBRID (mapa e satélite), ROADMAP (apenas mapa), TERRAIN (terreno: uma opção que fica abaixo do mapa) ou SATELLITE (visão do satélite, mas vi que ele habilita a opção mapa também, mas se escolher ela, o mapa não aparece, uma solução para usar apenas o satélite seria usar a propriedade mapTypeControl="false").
Resultado:

Capturar Latitude e Longitude

Seguindo ainda a mesma base usado anteriormente, irei usar para este exemplo minha classe LocalBean (vide imagem da estrutura do meu projeto lá em cima), nela faça o seguinte:
@javax.faces.bean.ManagedBean
@javax.faces.bean.ViewScoped
public class LocalBean {
    
    public void pontoSelecionado(PointSelectEvent event) {  
        LatLng latituteLongitude = event.getLatLng();  
          
        FacesContext.getCurrentInstance().addMessage(
            null,
            new FacesMessage(
                FacesMessage.SEVERITY_INFO, 
                "Ponto selecionado", 
                "Lat:" + latituteLongitude.getLat() + ", Long:" + latituteLongitude.getLng()
            )
        );  
    }  
}
Detalhes do código
Linha 5 e 6: as classes PointSelectEvent e LatLng devem ser importadas da biblioteca do PrimeFaces.


E a index deve ser editada para:
<h:form>
    <p:growl id="mensagem"  showDetail="true" />

    <p:gmap center="-15.565986,-54.309782" zoom="15" type="HYBRID" style="width:600px;height:400px">
        <p:ajax event="pointSelect" listener="#{localBean.pontoSelecionado}" update="mensagem" />
    </p:gmap>
</h:form>
Adicionamos tudo dentro de um h:form por conta do p:growl, então na linha 2 adicionamos o p:growl.
Linha 5: é adicionado a tag p:ajax, onde temos a propriedade event, listener que chama o método da nossa classe LocalBean, e por fim atualizamos o growl.
Resultando em:


Adicionando e carregando marcadores de um banco de dados
Para este exemplo estaremos gravando os marcadores em um banco de dados, e como disse anteriormente, estarei usando JPA para agilizar este desenvolvimento, então, para começo de conversa vamos fazer nossa classe model.Local:
@Entity
@Table(name="local")
public class Local implements Serializable{
    
    @Id
    @GeneratedValue(strategy= GenerationType.IDENTITY)
    @Column(name="codigo", nullable=false, unique=true)
    private Long codigo;
    @Column(name="descricao")
    private String descricao;
    @Column(name="latitude")
    private double latitude;
    @Column(name="longitude")
    private double longitude;

    /*
     * Getters e Setters
     */
}

Agora vamos fazer nossa classe dao.LocalDAO:
@Stateless
public class LocalDAO {
    
    @PersistenceContext(unitName="PrimeGmapPU")
    private EntityManager em;
    
    public void gravar(Local local){
        try {
            em.persist(local);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    
    public List<Local> listar(){
        List<Local> locais = new ArrayList<Local>();
        try {
            Query query = em.createQuery("SELECT l FROM Local l");
            locais = query.getResultList();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return locais;
    }
    
}

Lembrando que na Linha 4, o unitName, é da minha unidade de persistência, o que pode mudar de acordo como você criou sua conexão.
Agora vamos para nossa classe controller.LocalBean, eu refiz ela, ou seja tirei do projeto aquela questão do growl, ela ficou dessa forma:

@javax.faces.bean.ManagedBean
@javax.faces.bean.ViewScoped
public class LocalBean implements Serializable{

    @EJB
    private LocalDAO localDAO;
    private Local local;
    private MapModel locais;

    public void novo(PointSelectEvent event) {
        local = new Local();
        LatLng coord = event.getLatLng();
        local.setLatitude(coord.getLat());
        local.setLongitude(coord.getLng());
    }

    public void gravar() {
        localDAO.gravar(local);
        carregarLocais();
    }

    private void carregarLocais() {
        locais = new DefaultMapModel();

        List<Local> locaisList = localDAO.listar();
        for (Local l1 : locaisList) {
            locais.addOverlay(
                    new Marker(
                    new LatLng(l1.getLatitude(), l1.getLongitude()),
                    l1.getDescricao()));
        }
    }

    /*
     * Getters e Setters
     */
    public MapModel getLocais() {
        if (locais == null) {
            carregarLocais();
        }
        return locais;
    }

}

Linha 8: a classe MapModel deve ser importada da biblioteca do Primefaces;
Linha 10: método novo, responsável por pegar as coordenadas geográficas e setar no meu objeto Local;
Linha 17: método gravar, responsável por chamar o DAO e gravar o método no banco de dados;
Linha 22: método carregarLocais, este método é responsável por converter uma lista de locais em um MapModel;
Linha 37: neste caso estou mostrando apenas esse get, por ter um diferencial que é carregar os locais caso o mesmo esteja null.

E por fim nossa página index.xhtml:
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://java.sun.com/jsf/html"
      xmlns:p="http://primefaces.org/ui">
    <h:head>
        <script type="text/javascript" src="http://maps.google.com/maps/api/js?sensor=true" />
        <title>GMap</title>
    </h:head>
    <h:body style="font-size: 12px">
        <p:gmap id="mapa" center="-15.565986,-54.309782" zoom="15" model="#{localBean.locais}" onPointClick="dlgLocal.show()" type="HYBRID" style="width:600px;height:400px">
            <p:ajax event="pointSelect" listener="#{localBean.novo}" update="formLocal" />
        </p:gmap>

        <p:dialog widgetVar="dlgLocal" width="280" height="100">  
            <h:form id="formLocal">  
                <p:focus for="desc" />
                <h:panelGrid columns="2" style="width: 100%">
                    <h:outputLabel for="desc" value="Descrição:" />  
                    <p:inputText id="desc" value="#{localBean.local.descricao}" /> 
                </h:panelGrid>
                <p:toolbar>
                    <p:toolbarGroup align="right">
                        <p:commandButton value="Gravar" actionListener="#{localBean.gravar()}" oncomplete="dlgLocal.hide()" update=":mapa" />
                    </p:toolbarGroup>
                </p:toolbar>
            </h:form>  
        </p:dialog>
    </h:body>
</html>

Agora vamos entender o que está acontecendo nesta página:
Linha 9:  temos o gmap, com a propriedade model, que está relacionada ao MapModel do nosso controller, e a propriedade onPointClick, que abre o dialog de cadastro de um novo marcador;
Linha 10: chama o método novo quando é clicado no mapa;
Linha 13: temos o dialog que faz o cadastro de um novo marcador.
Ficando dessa forma:


Este projeto foi apenas para dar uma base de como carregar e adicionar marcadores no gmaps com o PrimeFaces. Também não tratei o dialog, ou seja, precisaria de um "trato especial" no caso dele não fechar quando o campo descrição estiver vazio. Bom eu tenho o código que faz isso, mas para não deixar essa postagem mais extensa, não coloquei aqui, provavelmente eu deva criar uma postagem sobre como controlar o dialog, até mesmo como abrir ou fechar ele pelo controller.

Até mais! :)

18 comentários:

  1. Ola.!

    Obrigado pelo post, realmente estava precisando de algo assim!

    ResponderExcluir
  2. Estou aqui tentando, o que preciso é o seguinte:

    Tenho uma listagem de terrenos.
    No dataTabel criei uma coluna assim:
    <p:commandButton id="basic" onclick="dlg1.show();" type="button" icon="ui-icon-circle-zoomin"

    Por cima abre o mapa.. ate ali blz.
    Cada terreno tem seu ponto localizado.. e ao abrir o selecionado deve puxar somente o ponto do objeto em questao.

    To apanhando um pouco mas acredito que vou conseguir.

    ResponderExcluir
    Respostas
    1. talvez vc tenha que passar essas coordenadas por parametro, ou jogar elas dentro de um atributo, algo desse tipo

      Excluir
  3. Este comentário foi removido pelo autor.

    ResponderExcluir
  4. Consigo integrar o componente GMAP e Geoxml3? Geoxml3 utiliza arquivos KML para pintar os mapas.

    ResponderExcluir
  5. O commandbutton nao chama o actionlistener ;(

    ResponderExcluir
  6. Grande... estou com um problema... talvez possa me ajudar.. estou precisando pegar as coordenadas... mas atraves do endereço digitado no formulario pelo usuario, teria alguma forma de fazer isso usando o primefaces ou até mesmo usando um backend para acessar o webservice?

    ResponderExcluir
  7. Este comentário foi removido pelo autor.

    ResponderExcluir
  8. Olá, eu tentei de todas as formas aqui, mais o erro que aparece é "listener="#{LocalMB.novo}": Target Unreachable, identifier 'LocalMB' resolved to null" localMB, ta substituindo LocalBean... pq é o padrão do meu projeto, to tentando encaixar a funcionalidade apresentada aqui é um projeto maior, referente ao meu TCC, teria como da uma ajuda, ou caso não tenha tendo, disponibilizar o codigo fonte para que eu posso analisar com mais cuidade, fico muito grato. att

    ResponderExcluir
  9. nao estou conseguindo colocar os pontos , como eu faço?

    meu list

    private void getListaMapasPostoAtendimento() {
    postoAtendimentoMap = new DefaultMapModel();
    PostoAtendimentoDAO postoAtendimentoDAO = new PostoAtendimentoDAO();
    List mapasPostosAtendimento = postoAtendimentoDAO.list(PostoAtendimento.class.getSimpleName());

    for (PostoAtendimento l1 : mapasPostosAtendimento) {
    postoAtendimentoMap.addOverlay(new Marker(new LatLng(l1.getLatitude(), l1.getLongitude()), l1.getNome_posto_atendimento()));
    }
    }

    public MapModel getPostoAtendimentoMap() {
    if (postoAtendimento == null) {
    getListaMapasPostoAtendimento();
    }
    return postoAtendimentoMap;
    }

    carrega o mapa mas não esta mostrando os pontos de longitude e altitude algume pode me ajudar?

    ResponderExcluir
  10. Em primeiro lugar, parabéns pelo belo post.
    Na linha: você manda re-renderizar o mapa. Esta atualização do componente p:gmap está funcionando vem para você, pois, no meu caso estou quebrando a cabeça pois, no request inicial o componente renderiza normalmente, porém, aco clickar no botão, ele é atualizado e fica todo cinza sem exibir o mapa. A diferença gritante que vejo do seu código para o meu é que o seu mapa está fora do form, ao contrario do meu. tem alguma idéia do que devo fazer para este update via ajax funcionar?

    ResponderExcluir
  11. muito didático o exemplo,parabéns!! sabe informar se esse componente tem integração com o geoserver + postgis?

    ResponderExcluir
  12. Gostaria de saber se tempo possibilidade de inserir o mapa com base no endereço, pois, tenho uma tabela no banco de dados com p. e. Rua Castro Alves, Cerâmica, São Caetano do Sul, SP e gostaria simplesmente de exibir o mapa com base neste endereço e não na Lat. e Log.

    Obribado.

    ResponderExcluir
  13. Boa tarde, estou precisando de ajuda no meu projeto. Estamos desenvolvendo um sistema de rasteamento e usamos o gps tk103b que nos retorna a longitude e a latitude via sms, mas queríamos que ele retorna-se esse posicionamento direto para o sistema. Alguém pode nos ajudar?

    ResponderExcluir
    Respostas
    1. vai precisar armazenar as coordenadas em um servidor e depois acessá-las.

      Excluir
  14. Fiz tudo minuciosamente linha por linha mas quando clico no mapa simplesmente não acontece nada. Você pode me ajudar? Por favor

    ResponderExcluir
  15. Por que aparece aquele "Ops deu errado!"?

    ResponderExcluir

Deixe seu comentário... ;)