Como procurar locais próximos usando SQL

Google Maps

Como procurar locais próximos usando SQL

Atenção! Essa postagem foi escrita há mais de 2 anos. Na informática tudo evolui muito rápido e algumas informações podem estar desatualizadas. Embora o conteúdo possa continuar relevante, lembre-se de levar em conta a data de publicação enquanto estiver lendo. Caso tenha sugestões para atualizá-la, não deixe de comentar!

E aí, galera! Tudo certo?

Estou fazendo alguns estudos com a API do Google Maps e isso me levou pensar de que maneira é possível encontrar endereços próximos utilizando SQL. Conduzindo minhas pesquisas, encontrei algumas coisas e gostaria de compartilhar.

Antes de mais nada, a Fórmula de Haversine!

Você deve estar se perguntando: o que diabos é Fórmula de Haversine? Não tema, pois a resposta está aqui!

A fórmula de Haversine é uma importante equação usada em navegação, fornecendo distâncias entre dois pontos de uma esfera a partir de suas latitudes e longitudes. É um caso especial de uma fórmula mais geral de trigonometria esférica, a lei dos Haversines, relacionando os lados a ângulos de uma esfera “triangular”.

Fonte: Wikipedia – Fórmula de Haversine

Basicamente, essa fórmula serve para fazer uma triangulação em uma esfera e ajuda encontrar pontos próximos de determinada coordenada geográfica. Leia mais sobre ela no artigo da Wikipedia.

Montando a consulta de triangulação

De posse da fórmula, precisamos convertê-la em SQL. Com base em um estudo disponibilizado na própria documentação da API do Google Maps, fiz algumas adaptações e montei a estrutura que apresento a seguir.

O primeiro passo é montar a tabela que irá armazenar os dados referente aos endereços. Para este exemplo, vamos utilizar uma tabela bastante simples, contendo apenas idnomeenderecolat (latitude) e lng (longitude). O endereço, neste caso, serve muito mais para complementação dos dados (ou para uma eventual busca pela geolocalização) do que propriamente para o cálculo.

Segue o SQL da tabela:

CREATE TABLE `enderecos` (
 `id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY ,
 `nome` VARCHAR( 60 ) NOT NULL ,
 `endereco` VARCHAR( 80 ) NOT NULL ,
 `lat` FLOAT( 10, 6 ) NOT NULL ,
 `lng` FLOAT( 10, 6 ) NOT NULL
) ;

Perceba que os valores lat e lng são do tipo ponto flutuante com 6 dígitos após a vírgula. Isso ocorre porque os valores de latitude e longitude são expressos como neste exemplo: -123.456789.

Criei também uma pequena lista de exemplo, contendo 5 shoppings de Porto Alegre – RS. Essas coordenadas vão nos ajudar a não começar de mãos vazias. Segue o comando para preencher o banco.

INSERT INTO `enderecos` (`id`, `nome`, `endereco`, `lat`, `lng`) VALUES
(NULL, 'Shopping Iguatemi Porto Alegre', 'Av. João Wallig, 1800 - Passo da Areia, Porto Alegre - RS', '-30.027668', '-51.163269'),
(NULL, 'Bourbon Shopping', 'Av. Assis Brasil, 164 - São João, Porto Alegre - RS', '-30.007913', '-51.184273'),
(NULL, 'Praia De Belas Shopping', 'Av. Praia de Belas, 1181 - Praia de Belas, Porto Alegre - RS', '-30.049527', '-51.228753'),
(NULL, 'Barra Shopping Sul', 'Av. Diário de Notícias, 300, Porto Alegre - RS', '-30.084494', '-51.245297'),
(NULL, 'Shopping TOTAL', 'Av. Cristóvão Colombo, 545 - Floresta, Porto Alegre - RS', '-30.025511', '-51.212344')

Montando a consulta SQL

Vamos montar uma consulta MySQL para encontrar todos os pontos em uma distância de até 25km, mostrando um máximo de 4 resultados por busca. Para fins de exemplo, peguei um ponto qualquer na Av. Ipiranga e vou utilizá-lo para calcular as distâncias. As coordenadas escolhidas foram -30.053831, -51.191810. Importante notar que a latitude aparece duas vezes no cálculo.

SELECT id,
(6371 * acos(
 cos( radians(-30.053831) )
 * cos( radians( lat ) )
 * cos( radians( lng ) - radians(-51.191810) )
 + sin( radians(-30.053831) )
 * sin( radians( lat ) ) 
 )
) AS distancia
FROM enderecos
HAVING distancia < 25
ORDER BY distancia ASC
LIMIT 4;

Como a fórmula é aplicada sobre uma esfera, é necessário informar o raio. O número 6371 diz respeito ao raio aproximado do planeta Terra, em quilômetros. Caso queira realizar o cálculo em milhas, utilize 3959. A consulta é ordenada por distância crescente, ou seja, mais próximos primeiro.

Resultado

Os resultados retornados estão funcionando perfeitamente. Não esqueça que essas distâncias são marcadas em linha reta, ou seja, se você for estabelecer um percurso de carro, é bastante provável que a distância aumente devido ao trajeto que deve ser feito.

id distancia
3 3.5876619973975385
5 3.7180529211314073
1 4.001380483066799
2 5.15708294670291

Dessa forma, conseguimos coletar os endereços mais próximos do ponto referenciado. Todos os endereços são próximos, então uma listagem sem limitação traria os 5 endereços, pois estão dentro do raio de 25km. Como a limitação foi 4, então apenas os 4 mais próximos são listados.

Espero que esse post ajude você a melhorar sua aplicação!

Pretendo, nos próximos dias, montar uma postagem indicando como inserir esses pontos no Google Maps.

Por enquanto é só, pessoal!

Um abraço a todos e fiquem com Deus,
Rafael Jaques

Fonte

Adaptado com base nos seguintes links:
https://developers.google.com/maps/articles/phpsqlsearch_v3
http://stackoverflow.com/questions/28973253/how-to-search-nearby-places-stored-in-my-database