terça-feira, 31 de janeiro de 2012

Informações sobre rotas com a versão 3 da API do Google Maps

No ano passado eu publiquei dois posts mostrando como traçar rotas usando a versão 3 da API do Google Maps. O primeiro tratava de um cenário básico onde consideramos um endereço de origem e outro de destino; o segundo avançava um passo ao permitir a inclusão de um ou mais pontos intermediários ao trajeto. Por razões distintas, várias pessoas me perguntaram como é possível obter a distância total entre a origem e o destino em uma rota, independendo se vamos efetivamente exibir a rota traçada.

Pra refrescar a memória, o quadro a seguir ilustra os comandos e estruturas básicos necessários pra trabalharmos com uma rota.
var directionsService, directionsRenderer;
var enderDe, enderAte;

directionsService = new google.maps.DirectionsService();
directionsRenderer = new google.maps.DirectionsRenderer();
directionsRenderer.setMap(map);

enderDe = 'ALAMEDA SANTOS, 1000, SÃO PAULO - SP, 01418-9028';
enderAte =
'AVENIDA NAÇÕES UNIDAS, 17-17, BAURU - SP, 17013-035';

var request = {
origin:endDe,
destination:endPara,
travelMode: google.maps.DirectionsTravelMode.DRIVING
};

directionsService.route(request, function(response, status) {
if (status == google.maps.DirectionsStatus.OK) {
directionsRenderer.setDirections(response);
}
});)

A linha directionsRenderer.setDirections (response) usa o objeto response para traçar automaticamente a rota no mapa. Esse objeto é do tipo DirectionsResult e é montado pela função route antes de ser repassado como response à nossa função de callback. No entanto, você pode usar manualmente as informações contidas em response sem que a rota precise ser exibida.

De acordo com a documentação, o DirectionsResult possui como única propriedade um Array chamado routes contendo a lista de rotas encontradas pelo serviço para atender a requisição feita. Cada elemento dessa lista traz detalhes de uma rota específica, descrita na forma de um objeto DirectionsRoute, o que inclui detalhes de cada trecho do trajeto, como a distância de cada um (em metros) e o tempo estimado usando o tipo de locomoção escolhido.

Com isso, podemos facilmente obter a distância a ser percorrida para chegar ao destino usando a rota em questão:
directionsService.route(request, function(response, status) {
if (status == google.maps.DirectionsStatus.OK) {
var distancia;
var rota = response.routes[0]; /* Primeira rota */
var etapa = rota.legs[0]; /* única etapa dessa rota */

distancia = etapa.distance.value;

alert ('Distância Total => ' + distancia.toString() + ' metros.');
}
});)

A propriedade legs destrincha cada etapa do trajeto usando objetos do tipo DirectionsLeg para descrevê-las. Por etapa entende-se o deslocamento necessário entre dois endereços. Assim, se você estipulou apenas a origem e o destino, haverá uma única etapa e, por conseguinte, uma única posição em legs. Por outro lado, se você adicionou pontos de referência usando o waypoints na requisição (veja esse post para mais detalhes), haverá uma nova etapa descrevendo como chegar a cada ponto. Nesse caso, será preciso percorrer todas as posições, somando a distância de cada etapa para se obter a distância total da rota:
directionsService.route(request, function(response, status) {
if (status == google.maps.DirectionsStatus.OK) {
var distancia = 0;
var rota = response.routes[0]; /* Primeira rota */
var etapa, i;

for (i = 0; i < rota.legs.length; i++)
{
/* Obtem cada etapa */
etapa = rota.legs[i];
distancia += etapa.distance.value;
}

alert ('Distância Total => ' + distancia.toString() + ' metros.');
}
});)

O detalhamento do caminho a ser seguido dentro de cada etapa do trajeto pode ser acessado através da propriedade steps. Cada passo também possui sua distância mas a soma delas já está consolidada na própria etapa (leg). Tenha em mente que, para certos trajetos, pode acontecer de as informações de distância e duração não estarem disponíveis. Por questão de simplicidade, os scripts acima não levam isso em conta. Um exemplo completo dos recursos mostrados até agora pode ser visto na página abaixo, cujo código fonte está neste link:

Um outro ponto importante a ser lembrado é que a função route é assíncrona. Isto significa que a função de callback que passamos a ela poderá ser executada depois da linha de código imediatamente seguinte ao route. Assim, se tiver que comparar as distâncias de várias rotas, terá que montar uma forma de sincronizar os cálculos para que a comparação só ocorra depois da execução de todos os callbacks envolvidos. Esse efeito normalmente é conseguido através da função javascript setinterval, verificando a cada segundo, por exemplo, se as rotas já estão prontas.

Mais Informações
Traçando rotas com a versão 3 da API do Google Maps - Parte I e Parte II, Google Maps JavaScript API V3

29 comentários:

  1. O link com o exemplo não funciona, ta certo mesmo?

    ResponderExcluir
    Respostas
    1. Eder

      O link do exemplo está correto e ativo. Se vc usa algum bloqueador de scripts (como o NoScript do Firefox), certifique-se de que o servidor onde está o exemplo está liberado. O exemplo está num outro servidor (não o do google) e isso pode barrar a execução dos scripts contidos nele.

      Excluir
    2. Agora funcionou, era o opera. Usando o chrome funcionou normal, existe uma função ou atributo que trasforma os metros em km? Por que geralmente quando vc traça uma rota a distancia total é em metros, e vi aqui que ta em km.

      Excluir
    3. A API realmente retorna as distâncias em metros. Mas, como as distâncias do exemplo são longas, eu as converto pra quilômetros pra ficar mais fácil de ler o resultado. Basta dividir o valor em metros por 1000 e você o tem convertido pra quilômetros.

      Excluir
    4. Ah blz, vlw Luis, muito util esses posts.

      Excluir
  2. Luís tenho mais uma dúvida, vc disse que o API retorna em metros, e como eu pego esses metros? No meu caso to desenvolvendo um sistema de transportadora, e queria pegar a distância para calcular preços.

    ResponderExcluir
    Respostas
    1. Eder

      Cada etapa (leg) do percurso traçado pela função de rota tem como membro um objeto distance cuja propriedade value traz a distância em metros. É exatamente o que mostra o post acima.

      []s

      Excluir
    2. No meu caso eu gostaria de mostrar a rota no mapa, e também os metros. A parte de mostrar no mapa ta funcionando, mas quando tento pegar os metros não consigo, sempre que uso "alert (Distância Total => + distancia.toString() + metros.);" fica tudo branco.

      directionsService.route(request, function(response, status) {
      if (status == google.maps.DirectionsStatus.OK) {
      directionsDisplay.setDirections(response);
      var distancia = 0;
      distancia += etapa.distance.value;
      }

      });

      Excluir
    3. Eder

      O código que você postou usa a variável etapa mas não aparece a declaração dela nem atribuição de valor a ela. Veja no código do post que ela deve receber uma etapa (leg) da rota retornada pelo directionsService.

      []

      Excluir
  3. Prezado Luís, parabéns pelo blog, muito bacana!
    Estou desenvolvendo um programa em PHP e queria a sua ajuda.
    Preciso buscar em um banco de dados, os 5 endereços mais próximos um do outro e traçar uma rota, é possível?
    Obrigado.

    ResponderExcluir
    Respostas
    1. André

      É possível conseguir esse resultado, sim. Se esta lista de endereços é predeterminada, creio que o caminho mais fácil seja popular as distâncias no próprio banco de dados, talvez usando as técnicas mostradas no post.

      Para traçar uma rota que inclua mais de um endereço, veja o post Traçando rotas com a versão 3 da API do Google Maps - Parte II.

      []s

      Excluir
    2. Luís, mais uma vez obrigado!
      Agora eu tenho outro obstáculo. Tenho um banco de dados com os endereços e suas respectivas coordenadas, e agora eu preciso pegar, em grupo de 5, os endereços mais próximos entre si.
      Eu tentei pegar um qualquer e buscar os 4 mais próximos dele, mas lá na frente os endereços que sobraram se separariam e não ficaria lógica a divisão.
      Sabe de alguma metodologia para este cálculo?
      Obrigado.

      Excluir
    3. André

      Desconheço o contexto com o qual você está trabalhando mas creio que você possa aplicar técnicas de Pesquisa Operacional para equacionar sua necessidade. Especificamente problemas relativos à otimização de itinerários podem ser resolvidos com a teoria do Caixeiro Viajante.

      Caso você esteja trabalhando com poucos endereços, pode manter registros das distâncias entre todos eles e orderná-los com base nessa informação, de acordo com a necessidade.

      Excluir
    4. Luís,

      o caixeiro viajante é bem interessante, porém ele utiliza todos os pontos para otimizar uma rota em todos estes, o que preciso é separar uma rota para cada grupo de 5 pontos, em que estes pontos sejam os mais próximos entre eles. Não descobri ainda, e se souber de algum método eu agradeço.
      No mais obrigado pela ajuda!

      Excluir
  4. Ola Luís, eu consegui implementar a parte de traçar rota e mostrar a distância, agora eu estou com uma dúvida, e se no caso eu for passar essa distância para uma variável php? Eu uso a função post do jquery, ou tem um jeito mais fácil?

    ResponderExcluir
    Respostas
    1. Eder

      Eu nunca trabalhei com PHP, de modo que não sei se ele te permite interagir com o javascript a ponto de retornar um valor para o servidor mais facilmente do que com JQuery. Em Asp.Net creio ser possível você lidar com isso através das variáveis repassadas na query string de um evento ou, ainda, com variáveis escondidas (hidden) no form HTML.

      []s

      Excluir
    2. Entendi, muito obrigado e continue com o blog, tem ótimas dicas.

      Excluir
  5. Olá, estou implementando isso em Delphi e to com uma duvida, se eu monto uma rota e depois eu modifico ela puxando ela pelo browser pela função Draggable, como eu faço que automaticamente o software mude pra mim sem eu precisar de um botão pra rodar o DirectionsResult novamente e montar a rota? não sei onde eu comparo que isso foi alterado... fazendo manualmente eu faço num botão e ele monta o trajeto novo que eu modifiquei normal, só nao sei como fazer isso automatico, que propriedade sei q foi modificado?
    Valeu

    ResponderExcluir
    Respostas
    1. Rodrigo

      Há um evento no DirectionsRenderer chamado directions_changed que serve para monitorar alterações na rota.

      Você terá que adicionar um "listener" ao DirectionsRenderer informando o nome do evento e a função para fazer o tratamento, como no exemplo abaixo:

      google.maps.event.addListener(mapDisplay, 'directions_changed', function(){
      processaRota (mapDisplay.directions.routes[0]);
      });

      Neste exemplo, mapDisplay é o meu DirectionsRenderer. Veja que a propriedade directions dele dá acesso à rota que está traçada no mapa.

      Lembre-se ainda que a rota traçada automaticamente também dispara esse evento.

      []s

      Excluir
  6. Nao consegui fazer, acho que nao entendi direito....
    Eu teria que colocar uma funçao na unit GMDirection para me retornar essa mudança?
    Obrigado....

    ResponderExcluir
    Respostas
    1. Rodrigo

      Não sei como está estruturada sua aplicação mas deu a entender que há uma unit pascal chamada GMDirection.pas. Essa unit é um encapsulamento da API 3 do googleMaps ?

      O código que inclui na resposta ao seu outro comentário é JavaScript; se vc está usando um encapsulamento Delphi para interagir com a API do Maps, terá que localizar nele como interceptar o evento directions_changed.

      Mas, se está usando um HTML com javascript dentro de um TWebBrowser no Delphi, pode usar diretamente o addListener para interceptar a mudança de rota. Nesse caso, fica mais fácil trabalhar com o HTML fora do Delphi antes pra ver como funciona.

      []s

      Excluir
  7. Posso usar essa api em meu aplicativo desktop, e comercializa-lo? É free..?

    ResponderExcluir
    Respostas
    1. Ribamar

      É permtido usar gratuitamente a API do Google Maps. No entanto, há certas restrições, principalmente em relação ao volume de acesso. Consulte os Temos de uso para se informar melhor e saber se o que vc quer fazer é permitido.

      []s

      Excluir
  8. Luis, na rota onde busco a distância entre 2 pontos funciona ok, mas como faço para que mostre uma linha reta entre eles, ao inves do trajeto que usa a variável DRIVING? grato e parabéns pelo blog.

    ResponderExcluir
  9. Luis, na rota entre 2 pontos tudo funciona ok,inclusive a distancia, porém como faço para mostrar uma linha reta entre os pontos ao inves do trajeto que usa a variável DRIVING.? grato e parabéns pelo blog.

    ResponderExcluir
    Respostas
    1. Alberto

      Você pode usar a classe Polyline do Maps para traçar linhas sobre um mapa. Essa classe cria uma nova camada sobreposta ao mapa, traçando uma linha que passa por todos os pontos (pares latitude/longitude) fornecidos à classe.

      []s

      Excluir
  10. Ola Luis,
    Eu tenho um mapa com 200 markers, como fazer para verificar qual deles é o mais proximo do cep digitado pelo usuário?

    ResponderExcluir
    Respostas
    1. Thiago

      Uma abordagem seria obter um par latitude/longitude para o CEP e calcular a distância entre esse ponto e cada um dos pontos determinados pelos marcadores, usando cálculo de distância linear simples entre 2 pontos.

      Uma solução mais complexa mas também mais precisa seria traçar as rotas entre o CEP digitado e cada um dos marcadores, obtendo o comprimento real de cada trajeto.

      Em ambas as soluções, o endereço que você procura é aquele com a menor distância calculada.

      []s

      Excluir

OBS: Os comentários enviados a este Blog são submetidos a moderação. Por isso, eles serão publicados somente após aprovação.