André Alves de Lima

Talking about Software Development and more…

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

Newsletter do André Lima

* indicates required



Powered by MailChimp

17 thoughts on “Calculando a distância entre dois pontos utilizando Google Maps e C#

  • Guilherme de Paula disse:

    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 ?

  • Alex disse:

    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

    • andrealveslima disse:

      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 […]

  • Renan disse:

    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?

  • Fábio Z disse:

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

    • andrealveslima disse:

      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:

              public static double DistanciaDireta(double lat1, double lat2, double long1, double long2)
              {
                  double arc1 = Higher(long1, long2) - Lower(long1, long2);
                  arc1 = (arc1 / 180) * Math.PI;
                  double arc2 = 90 - Higher(lat1, lat2);
                  arc2 = (arc2 / 180) * Math.PI;
                  double arc3 = 90 - Lower(lat1, lat2);
                  arc3 = (arc3 / 180) * Math.PI;
                  double distance = (40030 * Math.Acos((Math.Cos(arc2) * Math.Cos(arc3)) + (Math.Sin(arc2) * Math.Sin(arc3) * Math.Cos(arc1)))) / 360;
                  distance = (distance * 180) / Math.PI;
      
                  return distance;
              }
              private static double Higher(double value1, double value2)
              {
                  if (value1 > value2)
                      return value1;
                  else
                      return value2;
              }
              private static  double Lower(double value1, double value2)
              {
                  if (value1 < value2)
                      return value1;
                  else
                      return value2;
              }
      

      Não sei se te atende, mas, no nosso caso funcionou perfeito..

      Abraço!
      André Lima

      • Fábio Z disse:

        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,

        • andrealveslima disse:

          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

  • Vinícius Oliveira disse:

    Muito top seu exemplo (prático e esclarecedor)!
    Obrigado.

  • Claudio disse:

    Muito bom André, parabéns pelo conteúdo!!!!!!!!!!!!!!!!

  • 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

    • andrealveslima disse:

      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

Deixe uma resposta

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *