26 08 2015
Calculando a distância entre dois pontos utilizando Google Maps e C#
Quando eu era pequeno, me lembro de ter vivido na época em que, antes de viajar, era necessário consultar um mapa para saber por qual caminho ir. Até mesmo depois de ter tirado a minha carteira de habilitação, lembro de algumas situações em que eu usei o Guia 4 Rodas para gerar um mapa com o caminho que eu deveria seguir. Hoje em dia, isso é coisa do passado. Com o baixo preço dos aparelhos navegadores, nem nos preocupamos mais em saber o caminho até o nosso destino. Simplesmente entramos no carro, configuramos o destino no navegador e vamos que vamos. Se perdeu no meio do caminho? Sem problema, a rota é recalculada.
Mas onde é que eu quero chegar com essa história de mapa e aparelhos navegadores? Eu explico. Você deve saber muito bem que o desenvolvimento de aplicativos tem evoluído exponencialmente. A cada dia que passa, os nossos usuários esperam que nós implementemos funcionalidades que eram reservadas somente a sistemas supercomplexos. Um exemplo disso é o cálculo de distância entre dois pontos. Antigamente, se você tinha um aplicativo comercial, o máximo que você conseguiria informar para o usuário seria a distância direta entre dois pontos (usando coordenadas). O cálculo de distâncias mais precisas era reservado para sistemas logísticos, que tinham algoritmos extremamente complicados para fazer esse tipo de cálculo. Porém, hoje em dia o usuário não se contenta com essa informação imprecisa. O Google Maps está a um clique de distância e o usuário espera que consigamos fazer a integração com ele diretamente na nossa aplicação.
É por isso que, no artigo de hoje (que foi baseado em uma questão enviada por um dos inscritos na minha newsletter), mostrarei a você como calcular a distância entre dois pontos utilizando o Google Maps e C#.
A API de cálculo de rotas do Google Maps é baseada em web requests que retornam JSON. Isso quer dizer que, para calcular uma rota com o Google Maps através da nossa aplicação, basta fazermos uma requisição em uma URL específica e o retorno será um JSON com o resultado da rota.
Criando o projeto de exemplo
Para entendermos o funcionamento da API de rotas do Google Maps, vamos criar uma aplicação de exemplo bem simples, onde o usuário poderá indicar uma origem / destino e, ao clicar no botão de cálculo, os valores da distância e duração serão exibidos no formulário. Além disso, vamos exibir também dentro de um controle webbrowser o resultado de uma busca no Google Maps utilizando a mesma origem e destino. A interface deverá ficar parecida com isto:
Como você pode ver, a interface é bem simples. Temos quatro TextBoxes (origemTextBox, destinoTextBox, distanciaTextBox e duracaoTextBox), um botão (calcularButton) e um WebBrowser (mapaWebBrowser).
Além disso, é importante que você adicione uma referência ao assembly System.Web.Extensions, uma vez que iremos utilizar a classe JavaScriptSerializer para fazer o parse do JSON em classes do C#:
Convertendo o JSON do Google Maps para C#
Eu já mencionei anteriormente que o resultado da consulta na API de rotas do Google Maps é um JSON. Uma das opções que temos para interpretar esse JSON é fazer o próprio parsing do conteúdo retornado. Porém, essa não é a opção recomendada, uma vez que o código ficaria horrível e muito difícil de manter.
Uma ótima alternativa é gerarmos classes que representam o conteúdo do JSON retornado e utilizar o JavaScriptSerializer para converter o JSON em instâncias dessa classe. Para isso, temos que primeiramente ter um exemplo de retorno do JSON. A URL da API de rotas do Google Maps segue este padrão (onde XXXXX é a origem e YYYYY é o destino):
http://maps.googleapis.com/maps/api/directions/json?origin=XXXXX&destination=YYYYY&sensor=false
Para termos um exemplo de um JSON válido, podemos substituir XXXXX e YYYYY por uma origem e destino válidos. Por exemplo, substituindo a origem por “Limeira” (cidade onde eu nasci) e o destino por “Sao Paulo“, temos a seguinte URL:
http://maps.googleapis.com/maps/api/directions/json?origin=limeira&destination=sao paulo&sensor=false
Com essa URL, vamos até o site json2csharp, colamos a URL e clicamos no botão “Generate“. O resultado será um conjunto de classes que representam o JSON retornado pela URL que informamos:
Feito isso, copie o resultado gerado pelo site json2csharp, adicione uma nova classe no projeto (eu chamei de DistanceAPIClasses) e substitua o seu conteúdo pelo código gerado anteriormente:
public class GeocodedWaypoint { public string geocoder_status { get; set; } public bool partial_match { get; set; } public string place_id { get; set; } public List<string> types { get; set; } } public class Northeast { public double lat { get; set; } public double lng { get; set; } } public class Southwest { public double lat { get; set; } public double lng { get; set; } } public class Bounds { public Northeast northeast { get; set; } public Southwest southwest { get; set; } } public class Distance { public string text { get; set; } public int value { get; set; } } public class Duration { public string text { get; set; } public int value { get; set; } } public class EndLocation { public double lat { get; set; } public double lng { get; set; } } public class StartLocation { public double lat { get; set; } public double lng { get; set; } } public class Distance2 { public string text { get; set; } public int value { get; set; } } public class Duration2 { public string text { get; set; } public int value { get; set; } } public class EndLocation2 { public double lat { get; set; } public double lng { get; set; } } public class Polyline { public string points { get; set; } } public class StartLocation2 { public double lat { get; set; } public double lng { get; set; } } public class Step { public Distance2 distance { get; set; } public Duration2 duration { get; set; } public EndLocation2 end_location { get; set; } public string html_instructions { get; set; } public Polyline polyline { get; set; } public StartLocation2 start_location { get; set; } public string travel_mode { get; set; } public string maneuver { get; set; } } public class Leg { public Distance distance { get; set; } public Duration duration { get; set; } public string end_address { get; set; } public EndLocation end_location { get; set; } public string start_address { get; set; } public StartLocation start_location { get; set; } public List<Step> steps { get; set; } public List<object> via_waypoint { get; set; } } public class OverviewPolyline { public string points { get; set; } } public class Route { public Bounds bounds { get; set; } public string copyrights { get; set; } public List<Leg> legs { get; set; } public OverviewPolyline overview_polyline { get; set; } public string summary { get; set; } public List<object> warnings { get; set; } public List<object> waypoint_order { get; set; } } public class RootObject { public List<GeocodedWaypoint> geocoded_waypoints { get; set; } public List<Route> routes { get; set; } public string status { get; set; } }
Calculando a distância e duração
Agora que já temos as classes que serão utilizadas para fazer o parse do JSON, vamos ao código que fará uma consulta de rota no Google Maps. Faremos isso criando um método no nosso formulário chamado “CalcularDistanciaEDuracao“, que receberá a origem / destino e retornará a distância / duração. Veja como fica o código:
private bool CalcularDistanciaEDuracao(string origem, string destino, out double distancia, out double duracao) { bool sucesso = false; distancia = duracao = 0; try { string url = string.Format( "http://maps.googleapis.com/maps/api/directions/json?origin={0}&destination={1}&sensor=false", origem, destino); System.Net.WebRequest request = System.Net.HttpWebRequest.Create(url); System.Net.WebResponse response = request.GetResponse(); using (var reader = new System.IO.StreamReader(response.GetResponseStream())) { System.Web.Script.Serialization.JavaScriptSerializer parser = new System.Web.Script.Serialization.JavaScriptSerializer(); string responseString = reader.ReadToEnd(); RootObject responseData = parser.Deserialize<RootObject>(responseString); if (responseData != null) { double distanciaRetornada = responseData.routes.Sum(r => r.legs.Sum(l => l.distance.value)); double duracaoRetornada = responseData.routes.Sum(r => r.legs.Sum(l => l.duration.value)); if (distanciaRetornada != 0) { sucesso = true; distancia = distanciaRetornada; duracao = duracaoRetornada; } } } } catch { } return sucesso; }
Como você pode perceber, o código não é muito complicado. Primeiramente fazemos uma requisição utilizando a URL base que mencionei anteriormente, substituindo a origem e o destino recebidos pelo método. Com o resultado (que será um JSON), utilizamos a classe JavaScriptSerializer para desserializar o JSON em uma instância de RootObject (uma das classes criadas na etapa anterior). Dentro desse objeto, temos uma lista de rotas que, por sua vez, tem “Legs” (que nada mais são que cada passo da rota – vire aqui, vire ali, etc). Ao somarmos as distâncias e duração dos “Legs“, temos como resultado a distância (em metros) e a duração (em segundos) da melhor rota encontrada pelo Google Maps entre a origem e destino especificados.
Uma vez implementado esse método, podemos utilizá-lo no código do clique do botão “Calcular“:
private void calcularButton_Click(object sender, EventArgs e) { double distancia, duracao; if (CalcularDistanciaEDuracao(origemTextBox.Text, destinoTextBox.Text, out distancia, out duracao)) { distanciaTextBox.Text = string.Format("{0} m ({1:n2} km)", distancia, distancia / 1000); duracaoTextBox.Text = string.Format("{0} seg ({1:n2} min)", duracao, duracao / 60); mapaWebBrowser.Navigate(string.Format("http://maps.google.com/maps/dir/{0}/{1}", origemTextBox.Text, destinoTextBox.Text)); } else { MessageBox.Show("Não foi possível encontrar um caminho entre a origem e o destino.", "Erro", MessageBoxButtons.OK, MessageBoxIcon.Error); } }
Note que, além de preenchermos os TextBoxes com a distância e duração, também carregamos o WebBrowser com a mesma rota. Para maiores informações sobre os formatos de URL do Google Maps, confira este link.
Execute a aplicação e confira o resultado, por exemplo, da rota entre Limeira e São Paulo:
Veja que a distância e duração são calculados corretamente. Porém, o controle WebBrowser não consegue exibir o resultado da rota, porque esse controle utiliza o modo de compatibilidade do Internet Explorer. Para alterar esse comportamento, temos que adicionar uma chave no registro, conforme explicado nesta discussão no StackOverflow. Não esqueça de adicionar também uma entrada com a extensão “XXX.vshost.exe”, caso contrário, você não conseguirá ver o resultado ao debugar pelo Visual Studio:
Finalmente, após adicionarmos essas novas entradas no registro, conseguimos observar o resultado completo:
Bibliotecas GMap.NET e OsmSharp
Quando mencionei na minha newsletter que eu iria publicar um artigo sobre o cálculo de rotas com o Google Maps e C#, um dos inscritos, o Wellington Soares, prontamente me lembrou das bibliotecas GMap.NET e OsmSharp.
Eu tinha esquecido completamente da biblioteca GMap.NET, que eu já tinha utilizado para fazer uns testes no trabalho uns anos atrás. Ela nada mais é que um exemplo completo da implementação de diversas funcionalidades do Google Maps no Windows Forms. Recomendo que você dê uma olhada caso queira aprender mais sobre esse tema.
Já a biblioteca OsmSharp eu nunca utilizei, mas, parece muito interessante e profissional. Ela possibilita a exibição e adição de notas em cima de mapas vindos do OpenStreetMaps. Vale a pena dar uma conferida.
Concluindo
Não rara a situação em que temos que calcular a distância entre dois pontos nos nossos aplicativos. Basta termos a possibilidade de cadastrarmos endereços no nosso aplicativo e pronto, o usuário provavelmente desejará saber a distância entre dois endereços.
Nesse artigo você viu como descobrir a distância e a duração entre dois endereços (ou coordenadas) utilizando a API de rotas do Google Maps e C#. Agora é com você. Analise os seus projetos atuais e veja se essa funcionalidade encaixa em algum lugar que beneficie os seus usuários. Caso positivo, utilize esse tutorial e me fale depois qual foi o resultado.
E, antes de me despedir, gostaria de fazer um convite para que você assine a minha newsletter. Com ela, você receberá uma vez por semana no seu e-mail um resumo do artigo publicado e um preview do artigo da próxima semana, além de uma dica “bônus“, que eu só compartilho por e-mail! Assine agora mesmo através deste link ou utilizando o formulário logo abaixo.
Até a próxima!
André Lima
Utilizando o provider ADO.NET do MySQL no C# Armazenando bibliotecas em subdiretórios no C#
Fala André, tudo bem ? VOcê sabe se tem algum limite de requisições diárias ou até mesmo mensais da api, ou ela é 100% free ?
Olá Guilherme,
Os limites da conta gratuita estão disponíveis no seguinte link:
https://developers.google.com/maps/documentation/directions/intro#Limits
Como você pode ver, os limites atuais são de 2500 requisições por dia, máximo de 2 requisições por segundo (simultâneas)..
Abraço!
André Lima
Muito bom o artigo, ha algum tempo estava procurando uma solução deste tipo.
Andre, teria como escolher o percurso, mais longo, ou mais curto?
Sds
Alex
Olá Alex, obrigado pelo comentário! Fico feliz que você tenha gostado do artigo..
Sua pergunta é muito, muito interessante.. Por padrão, a API retorna somente o “melhor” caminho.. Para retornar rotas alternativas, você precisa passar o parâmetro &alternatives=true no final da URL de requisição:
string url = string.Format(
“http://maps.googleapis.com/maps/api/directions/json?origin={0}&destination={1}&sensor=false&alternatives=true”,
origem, destino);
Feito isso, a API não vai mais retornar somente uma rota, mas sim, todas as rotas possíveis.. Dessa forma, o seu objeto do tipo “RootObject” terá várias rotas:
Aí é só fazer um foreach pelas rotas retornadas e ver qual a distância / tempo necessário em cada uma delas..
Abraço!
André Lima
[…] principais APIs de roteamento disponíveis atualmente são: Google Maps, Bing Maps e Open Street Maps. Mas, André, não faz sentido para o meu aplicativo! Será? Se o seu […]
Muito bom o artigo, porém, estou com um baita problema em um programa que estou fazendo usando o GMap.NET e o OpenStreetMapProvider… como calculo a distância entre dois pontos?
Olá Renan, obrigado pelo comentário!
Nao sei exatamente como você implementou o seu projeto, mas, pode ser que este artigo te ajude:
GMap.NET Tutorial – Routes
Caso nao te ajude, por favor, dê maiores detalhes de como o seu projeto está estruturado..
Abraco!
André Lima
Boa noite André, parabéns belo belíssimo conteúdo e forma de explicação. Gostaria de saber qual seria a função para obter a distância direta entre dois pontos em C#. Estou desenvolvendo um sistema de alinhamento automático de antenas de transmissão móvel com GPS e Giroscópio, para isso, preciso obter a distância direta e calcular a triangulação de elevação para o sistema efetuar o apontamento de Tilt direto, o alinhamento do Pan está 100%. Desde já muito obrigado!!
Olá Fábio, obrigado pelo comentário!
Você quer dizer a distância direta a partir de latitude / longitude? A fórmula que utilizamos num sistema que eu trabalhei no passado foi esta:
Não sei se te atende, mas, no nosso caso funcionou perfeito..
Abraço!
André Lima
Olá André, atendeu sim, perfeitamente, muito obrigado mesmo. Desculpe-me a demora em lhe responder mas o projeto estava parado até o mês passado.
Criei até uma biblioteca para cálculo do apontamento direto conforme sua explicação e funcionou direitinho, criei outra biblioteca para o cálculo do Tilt (ângulo) e os dois estão muito precisos (erro de 1,2m à 10Km, totalmente aceitável), os sensores GPS, magnetômetro e atuadores da antena estão todos funcionais também.
Outro problema que arranjei, rsrs é o cálculo de obstáculos no apontamento. Com relação ao relevo do terreno Ok, como este exemplo simples que criei utilizando a API do google com 500 amostras de altitude do terreno entre dois pontos: (https://maps.googleapis.com/maps/api/elevation/json?path=-25.417252,-49.287260|-25.446998,-49.349938&samples=500).
O detalhe é que preciso validar também a altitude do terreno mais as alturas das construções que estão no percurso. As alturas não consegui obter com nenhuma função da API do google ou outros artifícios, mesmo nas APIs 3D.
Você teria alguma ideia para obter a altura das construções?
Novamente, muito obrigado.
Att,
Fala Fábio, tudo tranquilo?
Muito doido esse negócio que você está desenvolvendo hein.. Parece muito legal..
Quanto à questão da altura das construções, eu nunca precisei desse tipo de informação.. Parece que a API do Google não disponibiliza a altura das construções, mas, de acordo com uma thread no StackOverflow, o OpenStreetMap tem esse tipo de informação (não sei qual é a precisão e nem o quão coberto está o Brasil nessa questão).. Dê uma olhada nesta discussão:
Get height of a building from a maps API
Espero que te ajude em algo.. Depois volta aqui para avisar a gente como você conseguiu solucionar esse negócio..
Abraço!
André Lima
Muito top seu exemplo (prático e esclarecedor)!
Obrigado.
Olá Vinícius, muito obrigado pelo comentário! Fico feliz que tenha gostado do artigo.. :)
Um grande abraço!
André Lima
Muito bom André, parabéns pelo conteúdo!!!!!!!!!!!!!!!!
Muito obrigado Claudio!! Valeu pelo comentário!
Abraço!
André Lima
Da pra fazer de uma forma mais simples e ter mais recursos.
Existe uma biblioteca que tanto o entity é sal Server usam que resolve esse problema.
Estou para criar um post sobre isso.
assim que fizer é compartilho com vcs.
Se quiser pesquisar busque por dbgeography ou dbgeometry
Olá Paulo!
Obrigado pelo comentário! Que eu saiba, com as funcionalidades geográficas do SQL Server, você consegue calcular distâncias diretas entre duas coordenadas, o que é muito válido em alguns casos.. Porém, a funcionalidade demonstrada aqui nesse artigo é o cálculo da distância dentre duas coordenadas considerando a malha rodiviária entre os dois pontos (e não a distância direta entre os pontos).. Que eu saiba, isso não dá para fazer somente com as funcionalidade geográficas do SQL Server, ou dá?
Abraço!
André Lima