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

35 comentários :

Eder Cuer disse...

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

Luís Gustavo Fabbro disse...

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.

Eder Cuer disse...

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.

Luís Gustavo Fabbro disse...

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.

Eder Cuer disse...

Ah blz, vlw Luis, muito util esses posts.

Eder Cuer disse...

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.

Luís Gustavo Fabbro disse...

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

Eder Cuer disse...

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;
}

});

Luís Gustavo Fabbro disse...

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.

[]

André Hipólito disse...

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.

Luís Gustavo Fabbro disse...

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

André Hipólito disse...

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.

Luís Gustavo Fabbro disse...

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.

André Hipólito disse...

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!

Eder Cuer disse...

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?

Luís Gustavo Fabbro disse...

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

Eder Cuer disse...

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

Unknown disse...

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

Luís Gustavo Fabbro disse...

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

Unknown disse...

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....

Luís Gustavo Fabbro disse...

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

Unknown disse...

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

Luís Gustavo Fabbro disse...

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

Dir. Comunicação ANETI disse...

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.

Alberto Monteiro disse...

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.

Luís Gustavo Fabbro disse...

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

Luís Gustavo Fabbro disse...

Dê uma olhada no post que está no endereço http://balaiotecnologico.blogspot.com.br/2012/12/incluindo-camadas-personalizadas-em.html. Nele há um exemplo com o que vc quer fazer.

[]s

Thiago Enge disse...

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

Luís Gustavo Fabbro disse...

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

Unknown disse...

Luís Gustavo Fabbro, Boa noite!

Primeiro gostaria de parabenizar a excelencia do seu blog, e suas respostas esclarecedores.

Segundo, estou fazendo TCC e não estou achando diretamente e nem indiretamente qual o algoritmo utilizado pelo Google Maps para calcular as rotas, encontro apenas expeculações sobre a utilização do algoritmo A* combinado com "caminhos Atalho", e seria de extrema importancia pelo menos uma abordagem sobre o pseudocódigo do calculo, ou alguma informação mais precisa. Algo que eu possa comparar com os algoritmos de busca de caminhos que estão pela rede internet.

Caso saiba alguma dica, seria de grande valor !

Abraços

Luís Gustavo Fabbro disse...

Rafael

Não conheço o Google Maps por dentro, de modo que tb só posso especular. Pelas pesquisas que fiz, parece que o Google usa o algoritmo de Dijkstra para calcular rotas pelo caminho mais curto. É possível que seja usado em conjunto com o A* para otimizar certas etapas do cálculo.

Há uma discussão interessante sobre esse assunto neste fórum.

[]s

Marcio Batista disse...

Ola Luiz Gustavo, parabéns pelo seu excelente Blog

Tenho uma dúvida e ficarei muito grato caso você possa me ajuda. A dúvida é a seguinte:
Consigo fazer uma requisição de rota entre dois pontos para o Google, obter e decodificar a polyline recebida, bem como obter as coordenadas da polyline e traça a rota no mapa. O meu objetivo agora é montar uma consulta que apresenta essas rotas para um determinado usuário dependendo da proximidade dele com alguma das coordenadas dessas rotas (500 metro por exemplo). Ou seja o servidor mostra as opções de rotas próximas a ele independente da direção e sentido. Caso eu pegue as coordenado do usuário e faça uma consulta em todas as coordenadas de rotas armazenadas no banco de dados, com certeza vou obter um resultado, porém essa não é a forma mais otimizada de se realizar essa consulta caso eu tenha milhões de coordenadas de rotas.

Em fim a dúvida: como posso otimizar uma consulta para que o meu servidor com base nas coordenadas do usuário, faça uma pesquisa apenas em rotas que possam esta próxima dele ao invés de pesquisar as coordenadas de todas as rotas existentes no banco de dados?

Luís Gustavo Fabbro disse...

Marcio

Não sei como vc armazenou isso no banco de dados mas uma sugestão seria a seguinte, em linhas gerais: estabeleça um quadrado fictício cujo centro é a coordenada do usuário e, então, monte a consulta ao banco de dados para trazer apenas as coordenadas que estejam contidas nesse quadrado. Em tese, isso trará poucos resultados e vc poderá calcular a distância deles em relação à coordenada do usuário mais rapidamente.

Obs: Usar um círculo pode ser mais preciso mas a conta para determinar se uma coordenada está inserida nele será mais complexa.

[]s

Eduardo Elias disse...

Bom dia Luiz Gustavo, seu post foi de grande ajuda para mim em um projeto que estou implementando. Estou inserindo coordenadas no lugar dos endereços no código que você postou. Minha dúvida é a seguinte, existe a possibilidade de colocar o texto que eu desejar em cada ponto exibido no mapa? Ao clicar nos pontos, a própria API exibe o endereço que está o ponto, mas necessito colocar mais informações nestes pontos. Abraços.

Luís Gustavo Fabbro disse...

Eduardo

Você pode adicionar seus próprio marcadores (classe google.maps.Marker) e janelas com informações (classe google.maps.InfoWindow) em pontos do mapa. Veja um exemplo com ambos os casos no post Trabalhando com marcações em mapas com a versão 3 da API do Google Maps.

Postar um comentário

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.

Observação: somente um membro deste blog pode postar um comentário.