Ontem em uma conversa com um de nossos clientes, acabamos recebendo o relato de um problema para achar os locais mais próximos pela longitude e latitude.

Com isso em mente, pesquisamos um pouco como resolver isso de forma simples. Após algumas pesquisas, achamos vários sites explicando os cálculos que devem ser realizados e uma query para o MySQL que faz as devidas conversões, e nos retorna a distância entre os pontos. Segue abaixo os testes que realizamos para ver o funcionamento da query.

logistica-multipla-miniatura

Solução do problema

No nosso exemplo, gostaríamos de encontrar os pontos do mapa acima que estivessem mais próximos do escritório da Vizir, na Rua Boa Vista, 254 em São Paulo. Para fazer esta simulação fizemos os seguintes passos:

  • Criamos uma tabela para armazenar os endereços;
  • Inserimos os endereços com latitude e longitude;
  • Executamos a query para retornar os endereços mais próximos.
Tabela e insert com os endereços
CREATE TABLE endereco (
  id int(11) NOT NULL AUTO_INCREMENT,
  nome varchar(45) DEFAULT NULL,
  longitude varchar(45) DEFAULT NULL,
  latitude varchar(45) DEFAULT NULL,
  PRIMARY KEY (`id`)
)

-- Endereços para teste
INSERT INTO endereco(nome, longitude, latitude)
  VALUES('Terminal Mercado', '-46.62913200000003', '-23.5471634');
INSERT INTO endereco(nome, longitude, latitude)
  VALUES('Viaduto do Chá', '-46.63797820000002', '-23.5464833');
INSERT INTO endereco(nome, longitude, latitude)
  VALUES('Largo da Memória, 2-56', '-46.63892820000001', '-23.548348');
INSERT INTO endereco(nome, longitude, latitude)
  VALUES('Praça da República', '-46.64323139999999', '-23.5426444');
INSERT INTO endereco(nome, longitude, latitude)
  VALUES('Praça da Sé', '-46.63431400000002', '-23.5503342');
Executando a query

Utilizamos variáveis do MySQL para facilitar a reprodução do exemplo.

-- Select
-- Parâmetros de entrada
-- (Endereço da Vizir Rua Boa Vista 254)
set @orig_lat= abs(-23.5454668);
set @orig_lon= abs(-46.633525399999996);
set @distancia=10;

SELECT *,
	((3956 *
	2 *
	ASIN(
		SQRT(POWER(SIN((@orig_lat - abs(dest.latitude)) *
				   pi()/180 / 2),2) +
		COS(@orig_lat * pi()/180 ) *
		COS(abs(dest.latitude) * pi()/180) *
		POWER(SIN((@orig_lon - abs(dest.longitude)) *
					pi()/180 / 2), 2))
		)
    ) * 1.609344) as distancia
FROM endereco dest
having distancia < @distancia
ORDER BY distancia
limit 100;

Pronto, a query retornou os endereços ordenados por distância (em quilômetros).

Resultados obtidos
IdNomeLongitudeLatitudeDistância (km)
2Viaduto do Chá-4663797820000000-2354648330.46744009397678077
1Terminal Mercado-4662913200000000-2354716340.4856225533942553
5Praça da Sé-4663431400000000-2355033420.5467863930519828
3Largo da Memória, 2-56-4663892820000000-235483480.636702383775552
4Praça da República-4664323139999990-2354264441.0372715478873693

Se você quiser saber mais detalhes sobre a fórmula de Haversine, usada pra obter a distância mínima entre dois pontos, veja os links abaixo, que contém também uma apresentação explicando em detalhes como fazer o cálculo usando o MySQL.

Fonte

[1] http://en.wikipedia.org/wiki/Haversine_formula
[2] http://pt.scribd.com/doc/2569355/Geo-Distance-Search-with-MySQL