Haversine – Localizando Pontos na Esfera Terrestre Matematicamente

Tempo de Leitura: 5 Minutos

Imagine por exemplo que você precisa localizar todas as lojas que estão num raio de 3km de onde seu usuário está naquele momento, ou se um endereço está dentro da área de delivery de um restaurante, ou emitir um aviso para um cliente que está aguardando uma entrega avisando que o pedido está a menos de 1km essas e outras situações podem ocorrer, e é muito simples para serem executadas, sem o uso de aplicativos ou web-services de terceiros. Para esses casos, você deve conhecer ou pelo menos ter ouvido falar da fórmula de Haversine e exatamente sobre esse assunto que vamos falar neste post.

Se você já desenvolveu ou tentou estudar algo com mapas, geolocalização ou posicionamento global (GPS) ou mesmo tentou estudar a API do Google Maps, já deve ter se deparado com a fórmula de Haversine que é uma equação que fornece a distância entre dois pontos de uma esfera a partir de coordenadas de latitude e longitude. Na realidade, a fórmula quando aplicada a esfera terrestre é uma aproximação e pode ter um erro de 1% porque o planeta não é uma esfera perfeita e seu raio varia de 6356,78 km nos pólos até 6378,14 km no equador. Estas pequenas correções, na ordem de 0,1% (supondo R = 6367,45 km) são usadas em todo lugar, devido a leve forma elipsoide do nosso planeta. Mais vamos a parte mais técnica e menos matemática por traz disso.

A Casa Buon Gusto agora é Olga Ri | Área de Entrega Neste exemplo, aplicando a fórmula de haversine vamos localizar um ponto por exemplo de um cliente que quer fazer um pedido por delivery, e retornar todos os estabelecimentos que estão dentro de um raio de 5km para isso, temos no nosso banco de dados uma tabela de estabelecimentos como segue:

CREATE TABLE lojas ( id int(10) UNSIGNED ZEROFILL NOT NULL, nome varchar(200) COLLATE utf8_unicode_ci NOT NULL COMMENT 'Nome do estabelescimento', latitude float(11,8) NOT NULL COMMENT 'Latitude (decimal, precisão de 8)', longitude float(11,8) NOT NULL COMMENT 'Longitude (decimal, precisão 8)', distancia decimal(4,2) NOT NULL COMMENT 'Distancia em KM de delivery' ) ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_unicode_ci;

Onde, a tabela contém 3 campos que são interessantes, LATITUDE, LONGITUDE e DISTANCIA e somente com base nestes 3 campos, podemos criar a consulta baixo:

SELECT *, (6371 *
        acos(
            cos(radians(-19.83996)) *
            cos(radians(latitude)) *
            cos(radians(-43.94910) - radians(longitude)) +
            sin(radians(-19.83996)) *
            sin(radians(latitude))
        )) AS distance
FROM lojas HAVING distance <= 5

explicando cada parte dessa consulta, e trocando-a por variáveis, temos:

SELECT *,(RAIO_TERRESTRE * 
        acos(
         cos(radians(PARAMETRO_LATITUDE)) * 
         cos(radians(COLUNA_LATITUDE)) * 
         cos(radians(PARAMETRO_LONGITUDE) - radians(COLUNA_LONGITUDE)) + 
         sin(radians(PARAMETRO_LATITUDE)) * 
         sin(radians(COLUNA_LATITUDE))
      )) AS CAMPOLATITUDE
FROM TABELA HAVING CAMPOLATITUDE <= KM

RAIO_TERRESTRE = Tamanho da terra, usamos um valor mais propicio para a cidade de são paulo, você pode usar um padrão 6367,45 que é o determinado para cálculos com distâncias maiores, e que tem uma precisão de 0,1% ou seja 1 metro em uma distância de 1Km

PARAMETRO_LATITUDE e PARAMETRO_LONGITUDE = São as coordenadas geográficas onde se encontra nosso usuário, ou seja, o cliente.

COLUNA_LATITUDE e COLUNA_LONGITUDE são as colunas da nossa base de dados, onde os estabelecimentos estão cadastrados

CAMPOLATITUDE = a distância em KM que determinado estabelecimento está do nosso usuário

Vamos inserir 4 endereços de pontos turísticos de São Paulo na nossa tabela

id nome Nome do estabelescimento latitude Latitude (decimal, precisão de 8) longitude Longitude (decimal, precisão 8) distancia Distancia em KM de delivery
0000000001 Catedral da Sé -23.55030060 -46.63389206 5.00
0000000002 Mercado Municipal SP -23.54157066 -46.62905884 5.00
0000000003 Mercado da Lapa -23.51879501 -46.70182037 5.00
0000000004 Aquario de SP -23.58993530 -46.61406708 1.00

Agora vamos supor que nosso usuário está na estação sé e quer conhecer o que tem mais perto dele, no caso a catedral e o mercado, usamos a seguinte consulta:

SELECT *, (6371 *
        acos(
            cos(radians(-23.550176)) *
            cos(radians(latitude)) *
            cos(radians(-46.633196) - radians(longitude)) +
            sin(radians(-23.550176)) *
            sin(radians(latitude))
        )) AS distance
FROM lojas HAVING distance <= 3

que vai nos retornar somente 2 resultados dentro da área de 3 KM:

id nome Nome do estabelescimento latitude Latitude (decimal, precisão de 8) longitude Longitude (decimal, precisão 8) distancia Distancia em KM de delivery distance
0000000001 Catedral da Sé -23.55030060 -46.63389206 5.00 0.07229184244769615
0000000002 Mercado Municipal SP -23.54157066 -46.62905884 5.00 1.0456842237642823

Veja que esta consulta também nos retorna a distância em KM do ponto escolhido. Ou seja 72m da catedral, e 1,04km do mercado municipal.

Detalhes Sobre GEOLOCALIZAÇÃO

Veja, que em nossa tabela, usamos para armazenar os dados de latitude e longitude uma precisão de 8 casas decimais, com essa definição, e com a circunferência da terra sendo passada como a da cidade de São Paulo, no seu marco zero, a precisão das distâncias já com a margem de erro é de 1,7 metros para mais ou para menos, arredondando os numeros acima para 3 casas decimais, nossa variação máxima é de 3m para uma distância de 10km de distância do ponto central da localização do usuário.

A Maioria dos equipamentos, inclusive o google maps e os equipamentos de GPS usam como padrão a circunferência da terra como sendo 6367,45 ou simplesmente 6367 e os dados de latitude e longitude com uma precisão de 6 casas decimais (FLOAT (9,6) ou FLOAT(10,6) respectivamente) o que dá uma precisão de 3m para mais ou para menos em uma distância de 20km não considerando a própria curvatura da terra.

Só para se ter uma idéia, com uma precisão de inteiros, sem casas decimais é possível uma variação máxima de 1,57Km e se distinguir uma cidade da outra em qualquer parte do planeta.  Se nossa precisão for de 2 casas decimais, truncando o resultado, a precisão é de 1,5Km e é possível identificar bairros e cidades. Se usarmos 2 casas decimais, arredondando o calculo essa precisão aumenta para 680m de variação, e já é possível identificar bairros em uma rota com veiculos. Com 4 casas decimais truncado, a precisão é de 16m, se forem 4 casas arredondadas a precisão é de 2,7m e definir rotas a pé, em 6 casas decimais arredondadas, a precisão é de 1,7m e é o mais utilizado devido a rapidez do calculo e baixo consumo de hardware, no caso 6 casas truncado, a variação é de somente 16cm. Com 8 casas decimais é possível uma variação de 1,6cm a cada km e é considerado uma forma de localizar distintamente 2 pessoas. Com as 16 casas decimais definidas a variação só da curvatura é de 1.6mm para cada km calculado, e é possível se identificar uma pulga em um cachorro. (essas definições foram retiradas de: http://mysql.rjweb.org/doc.php/latlng) onde encontramos mais alguns estudos com cálculos de geolocalização.