Calculando distâncias com Geolocalização pelo MySQL
Geolocalização está no topo das tecnologias atuais pois junto com os dispositivos móveis vieram também inúmeras oportunidades de aplicativos que utilizam a localização do usuário para uma infinidade de coisas, de dizer se um amigo está por perto a indicar restaurantes ou serviços próximos a onde o usuário está.
Sendo assim é imprescindível aprendermos a trabalhar com geolocalização.
A Geolocalização é fundamentalmente um serviço fornecido pelo "Front End" ou falando de modo mais geral do lado cliente, pois é o browser ou o aparelho móvel quem disponibiliza esta informação. Apesar disto existem coisas relacionadas com Geolocalização que sim podem (e devem) ser feitas do lado servidor (Server Side).
Se você está criando alguma aplicação relacionada a encontrar locais próximos, por exemplo, caso não esteja usando um web service de terceiros, no mínimo um banco de dados com as coordenadas geográficas de vários locais você provavelmente deverá possuir.
Vamos supor que você tenha uma tabela em seu banco de dados assim:
Latitude | Longitude | Nome do Restaurante | Endereço
-45.7855 | -46.3590 | Quinta da Boa Mesa | Rua Avaré 55
-44.5697 | -47.6885 | São Salvador | Avenida Moreira Filho 10
A questão é: como encontrar o restaurante mais próximo de onde o usuário está?
A resposta óbvia será medir a distância entre onde você está e os restaurantes de sua lista, porém fazer isto não é simplesmente fazer um cálculo de triângulos, como aprendemos na escola, este cálculo é bem mais complicado pois devemos lembrar que estamos na superfície de uma ESFERA (sim, a terra é redonda).
Sendo assim o cálculo trigonométrico deve levar em consideração esta curvatura da Terra. Em resumo as distâncias serão sempre um pouco maiores do que se fosse feito um cálculo trigonométrico em uma superfície plana.
Para efetuar estes cálculos temos que trabalhar com quatro coordenadas: latitude e longitude de onde o usuário está e latitude e longitude de sua lista de restaurantes.
Podemos adotar várias abordagens para fazer este cálculo. Podemos fazer o cálculo diretamente no lado cliente (pegando os valores do banco e efetuando os cálculos pelo javascript, Java ou Objective C), podemos fazer do lado Servidor pelo PHP, Ruby, JSP, ASPX, etc. ou do lado Server mas diretamente no Banco de dados.
A abordagem que quero mostrar é aquela utilizando justamente o Banco de Dados pois, além de o considerar mais rápido e limpo, o DB nos permite fazer coisas muito interessantes e de maneira mais prática como ordenar as distância de forma muito tranquila, além de, no meu ponto de vista, fazer mais sentido tratar estas distâncias dentro de um banco de dados.
Para isso vamos criar uma função dentro do DB que efetuará os cálculos de distância e os trará como um campo extra.
Segue a função completa e logo abaixo dela explico os pontos importantes:
delimiter //
CREATE FUNCTION Geo(lat_ini FLOAT(18,10), lon_ini FLOAT(18,10),lat_fim FLOAT(18,10), lon_fim FLOAT(18,10))
RETURNS FLOAT(18,10)
NOT DETERMINISTIC
BEGIN
DECLARE Theta FLOAT(18,10);
DECLARE Dist FLOAT(18,10);
DECLARE Miles FLOAT(18,10);
DECLARE kilometers FLOAT(18,10);
SET Theta = lon_ini - lon_fim;
SET Dist = SIN(RADIANS(lat_ini)) * SIN(RADIANS(lat_fim)) + COS(RADIANS(lat_ini)) * COS(RADIANS(lat_fim)) * COS(RADIANS(Theta));
SET Dist = ACOS(Dist);
SET Dist = DEGREES(Dist);
SET Miles = Dist * 60 * 1.1515;
SET kilometers = Miles * 1.609344;
RETURN kilometers;
END;//
delimiter ;
- O comando delimiter // muda o delimitador padrão de ; para //. Isto nos permite escrever a função utilizando o separador de comando correto. Ao concluir a função retornamos o delimitador padrão para ;.
- Ao criar a função 'CREATE FUNCTION' damos um nome a ela ('Geo') e declaramos os parâmetros de entrada, inclusive os tipos e espaço em memória.
- 'lat_ini' e 'lon_ini' são as coordenadas de origem. No caso de uma aplicação em que utilizamos as coordenadas do usuário para calcular distâncias, 'lat_ini' e 'lon_ini' são as variáveis que receberão estes dados e 'lat_fim' e 'lon_fim' são as coordenadas dos estabelecimentos ou locais até onde você deseja fazer o cálculo. Neste contexto 'lat_ini' e 'lon_ini' serão fixos de um dado usuário e 'lat_fim' e 'lon_fim' poderão ser coordenadas presentes no banco de dados para vários estabelecimentos.
- 'RETURNS FLOAT' define o tipo que será retornado em 'RETURN kilometers'
- Os DECLARE também instanciam as variáveis assim como na declaração da função, a diferença é que estas variáveis são internas.
- SET atribui o valor dos cálculos a uma variável. No caso de nossa função os cinco primeiros comandos SET fazem o cálculo de geolocalização (em milhas) e o ultimo SET converte milhas para quilômetros.
Aplicar esta função é muito simples. Basta incluí-la em seu SELECT.
SELECT Geo(-34.50696,-33.438673,Latitude, Longitude) AS Distancia, Restaurante FROM tabela
Como resposta vc deve ter algo como:
Distancia | Restaurante
9.6585 | Quinta da Boa Mesa
5.9656 | São Salvador
É isso ai!
Se esta dica foi útil a você compartilhe o link nas redes sociais.
Ola amigo, esta dando erro na criação da funcao, nesta linha:
ResponderExcluirDECLARE Theta FLOAT(18,10);
Este comentário foi removido por um administrador do blog.
ResponderExcluirPARABÉNS PELA FUNÇÃO, SIMPLISMENTE SALVOU MINHA VIDA
ResponderExcluirMUITO OBRIGADO
Obrigado por visitar o Blog. Abraços
Excluirporque só uma loja apareceu os dados corretos, e as outras com os dados da distancia "0.0000000000", fiz o teste com 4 lojas, e só uma fica com a distancia aparecendo aparece "4.3991804123".... Obrigado desde já
ResponderExcluirEstou com o mesmo problema. Aparece "0.000000" nos registros
ExcluirResolvi o problema simplesmente trocando todos os FLOAt por DOUBLE
ExcluirVlw, trocar por Double resolveu aqui também =D
ExcluirOlá Sasuke e obrigado pelo comentário.
ResponderExcluirDe uma revisada nos dados de latitude e longitude de seu banco. Verifique também se a latitude e longitude da localização de referência está correta.
Abraço
Estou com o mesmo problema do sasuke, mas não tem como os dados estarem errados, peguei no google maps
ExcluirResolvi o problema simplesmente trocando todos os FLOAt por DOUBLE
ExcluirExcelente! Obrigado pela contribuição!
Excluir