Cálculo de distância entre locais usando coordenadas

27 respostas
A

Olá amigos do fórum.

Venho postar aqui uma duvida que quiça algum colega do fórum possa me ajudar. Peço desculpas ao moderadores se a pasta “Off-Topic” é inadequada

Bem, amigos, gostaria de implementar uma funcionalidade em um projeto que estou desenvolvendo, que é uma espécie de busca por “locais próximos”. Tenho na minha base as coordenadas dos locais possíveis de serem encontrados, e gostaria de consultar os registros usando um filtro de distância, dada uma coordenada.

Algo como “meu local é [meu endereço], quero localizar na base tudo o que está em um raio de x km daqui”. Existe alguma maneira de se fazer esse “calculo” usando a latitude e longitude?

Obrigado amigos!

27 Respostas

V
W

Procure pela fórmula de Haversine para um cálculo de distância preciso.

Se você estiver usando um banco de dados com suporte a dados espaciais provavelmente você já tem uma função que faz o que você quer. No caso do Postgis você pode usar st_dwithin, por exemplo.

A

Obrigado, pessoal. Irei me aprofundar nas alternativas citadas. A base que utilizo é o MySQL, se houver uma alternativa como a que o colega wagnerfrancisco citou seria de muita ajuda.

Uma duvida que me ocorre para uma pesquisa dessa, existe uma relação de equidistancia entre coordenadas? Um exemplo tosco, se tenho uma coordenada lat 500, lng 500, em uma distancia de 500m as coordenadas seriam lat 1000, lng 1000? Desculpem a viagem, não entendo bulhufas dessa ciência. Embora se fosse assim seria fácil demais pra ser verdade :lol:

Valeu, pessoal.

R

alias:
Obrigado, pessoal. Irei me aprofundar nas alternativas citadas. A base que utilizo é o MySQL, se houver uma alternativa como a que o colega wagnerfrancisco citou seria de muita ajuda.

Uma duvida que me ocorre para uma pesquisa dessa, existe uma relação de equidistancia entre coordenadas? Um exemplo tosco, se tenho uma coordenada lat 500, lng 500, em uma distancia de 500m as coordenadas seriam lat 1000, lng 1000? Desculpem a viagem, não entendo bulhufas dessa ciência. Embora se fosse assim seria fácil demais pra ser verdade :lol:

Valeu, pessoal.

De maneira alguma. Latitude e longitude são coordenadas angulares de pontos na superfície da terra. Por exemplo, a latitude é o ângulo entre o equador e o segmento de reta que passa pelo ponto que você localiza e o centro da Terra. Esse cálculo fica um pouco mais complicado se você levar em conta que a Terra não é uma esfera perfeita.

A

rmendes08:
alias:
Obrigado, pessoal. Irei me aprofundar nas alternativas citadas. A base que utilizo é o MySQL, se houver uma alternativa como a que o colega wagnerfrancisco citou seria de muita ajuda.

Uma duvida que me ocorre para uma pesquisa dessa, existe uma relação de equidistancia entre coordenadas? Um exemplo tosco, se tenho uma coordenada lat 500, lng 500, em uma distancia de 500m as coordenadas seriam lat 1000, lng 1000? Desculpem a viagem, não entendo bulhufas dessa ciência. Embora se fosse assim seria fácil demais pra ser verdade :lol:

Valeu, pessoal.

De maneira alguma. Latitude e longitude são coordenadas angulares de pontos na superfície da terra. Por exemplo, a latitude é o ângulo entre o equador e o segmento de reta que passa pelo ponto que você localiza e o centro da Terra. Esse cálculo fica um pouco mais complicado se você levar em conta que a Terra não é uma esfera perfeita.

Obrigado, meu velho. Santa ingenuidade a minha :lol:

Valeu novamente, galera.

A

wagnerfrancisco:
Procure pela fórmula de Haversine para um cálculo de distância preciso.

Se você estiver usando um banco de dados com suporte a dados espaciais provavelmente você já tem uma função que faz o que você quer. No caso do Postgis você pode usar st_dwithin, por exemplo.

Achei um conteudo na documentação do MySql ref. a dados espaciais. Valeu pelo toque, amigo. Vou estudar a coisa e posto aqui o caminho que tomei.

Valeu pessoal.

V

Você leu o link que passei?

A

Sim! :wink: …embora esteja me perguntando como passar o calculo que você citou em um “where” na base.

L

acho que o google tem APIs e soluções prontas para isto…
se não for trabalho acadêmico só vai perder tempo em fazer as algebraradas do capeta… usa direto a api do google e pronto…

Cara isto é muito simples! pega da sua base a coordenada de destino e de partida apenas isto. Usa a api do google para te devolver quantos km é do ponto inicial até o final…

W

O Google realmente disponibiliza este serviço, mas se não me engano, pra fins comerciais, é preciso usar a api premium, que é paga.

alias, como eu mencionei antes, o postgis tem uma função que faz exatamente o que tu precisa. Dado um conjunto de pontos (que você armazenará em uma tabela, por exemplo), você pode buscar todos que estejam ao redor de um ponto qualquer (sendo que você especifica o raio). A consulta seria mais ou menos assim:

select * from points where st_dwithin(st_geometry_from_text('POINT(10 10)'), the_geom, 100);

st_geometry_from_text é uma função que cria um objeto espacial, mas este valor poderia vir de outro lugar (por exemplo, uma subconsulta). the_geom é o nome da coluna que contem dados espaciais na tabela points e 100 é o raio que você quer buscar.

Tem, inclusive uma extensão do Hibernate pra trabalhar com este tipo de dado, o Hibernate Spatial (http://www.hibernatespatial.org/).

Nunca usei MySQL pra trabalhar com este tipo de informação, mas na documentação do Hibernate Spatial tá escrito que há suporte. Não sei se é 100%, vale a pena checar…

Falou!

A

Valeu novamente, wagnerfrancisco…caramba, se eu soubesse que ia precisar disso, teria usado o Postgre! O framework que voce citou não dá suporte 100% às funcionalidades de dados espaciais no Mysql, simplesmente pq…algumas funções ainda nem estão implementadas no Mysql! :lol: …em especial a função distance

Obrigado pela luz, cara, estou encontrando alguns “workarounds” internet afora…achei umas procedures que fazem aquele calculo que o Vini citou tambem! :shock:

Valeu pessoal.

A

wagnerfrancisco:
O Google realmente disponibiliza este serviço, mas se não me engano, pra fins comerciais, é preciso usar a api premium, que é paga.

alias, como eu mencionei antes, o postgis tem uma função que faz exatamente o que tu precisa. Dado um conjunto de pontos (que você armazenará em uma tabela, por exemplo), você pode buscar todos que estejam ao redor de um ponto qualquer (sendo que você especifica o raio). A consulta seria mais ou menos assim:

select * from points where st_dwithin(st_geometry_from_text('POINT(10 10)'), the_geom, 100);

st_geometry_from_text é uma função que cria um objeto espacial, mas este valor poderia vir de outro lugar (por exemplo, uma subconsulta). the_geom é o nome da coluna que contem dados espaciais na tabela points e 100 é o raio que você quer buscar.

Tem, inclusive uma extensão do Hibernate pra trabalhar com este tipo de dado, o Hibernate Spatial (http://www.hibernatespatial.org/).

Nunca usei MySQL pra trabalhar com este tipo de informação, mas na documentação do Hibernate Spatial tá escrito que há suporte. Não sei se é 100%, vale a pena checar…

Falou!

Prezado wagnerfrancisco, já estou considerando migrar minha base para o Postgree dada essa limitação do MySQL. Sabe dizer se o PostGIS é um “complemento” do Postgree, é um produto à parte, ou o suporte a dados espaciais, e essas funções que citou, já são instaladas por padrão?

Obrigado!

W

Legal cara, tu vai gostar.

O PostgreSQL tem suporte à alguns dados espaciais, mas estas funções mais complexas estão no PostGIS, que, como você disse, é um complemento. No Linux você pode instalar o postgis por meio de algum repositório da tua distribuição. No Windows, tem uma ferramenta chamada StackBuilder que vem junto quando tu instala o PostgreSQL. É só usar ela pra instalar o PostGIS.

Depois, quando tudo tiver instalado, você cria uma nova base de dados usando uma base do postgis como template (pode ser tudo pelo pgAdmin mesmo).

Falou!

A

wagnerfrancisco:
Legal cara, tu vai gostar.

O PostgreSQL tem suporte à alguns dados espaciais, mas estas funções mais complexas estão no PostGIS, que, como você disse, é um complemento. No Linux você pode instalar o postgis por meio de algum repositório da tua distribuição. No Windows, tem uma ferramenta chamada StackBuilder que vem junto quando tu instala o PostgreSQL. É só usar ela pra instalar o PostGIS.

Depois, quando tudo tiver instalado, você cria uma nova base de dados usando uma base do postgis como template (pode ser tudo pelo pgAdmin mesmo).

Falou!

Ih mano, isso pode ser um problema. Eu teria que pedir ao administrador do meu host para instalar esse complemento. Quer dizer, o host permite instalar bases Postgree, mas não sei se esse complemento está disponível. Se não estiver e a empresa se recusar a instalar, fodeu :cry:

Dadas as limitações do MySQL encontrei algumas funções e procedures internet afora que, pra calcular a distância, algumas utilizam a formula que o Vini passou e outras a formula de Haversine que você havia citado. Em ultimo caso meu plano é customizar essas funções e adequa-las a minha base, ia ficar sujinho mas fazer o que :lol: . Se tudo der certo eu vou de Postgree, se não…

Valeu mesmo pelas informações, cara. Brigadão.

F

Já pensou em utilizar bancos de dados NoSQL? Uma das aplicações mais comuns é o georeferenciamento, muitos deles já possuem suporte nativo.

Olha como é simples uma consulta no MongoDB, por exemplo:

db.places.find( { loc : { $near : [50,50] , $maxDistance : 5 } } ).limit(20)

fonte: http://www.mongodb.org/display/DOCS/Geospatial+Indexing

A

fabiocsilva:
Já pensou em utilizar bancos de dados NoSQL? Uma das aplicações mais comuns é o georeferenciamento, muitos deles já possuem suporte nativo.

Olha como é simples uma consulta no MongoDB, por exemplo:

db.places.find( { loc : { $near : [50,50] , $maxDistance : 5 } } ).limit(20)

fonte: http://www.mongodb.org/display/DOCS/Geospatial+Indexing

Cara valeu pela dica! Eu havia mesmo lido em algum lugar que o MongoDB suportava geo-indexação. É uma alternativa interessante, mas utiliza-lo teria muito impacto na minha aplicação, penso eu. Você concorda? Hoje já tenho as entidades todas mapeadas no Hibernate, usando o MySQL. Pra usar o Postgree (como eu tinha dito antes) seria só alterar a config do Hibernate…você acha que seria viável utilizar duas bases, mantendo o legado que tenho hoje e usando o MongoDB para armazenar os dados espaciais? Se bem que ainda que seja viável ia dar um trabalho pra implementar…

O seu exemplo me trouxe uma duvida que talvez possa ajudar, e tambem voltando a esse exemplo que o colega wagnerfrancisco havia mostrado:

No caso do MongoDB, essa distancia passada como filtro é em milhas? Prezado wagner, sabe dizer qual é a medida utilizada no Postgree?

Obrigado!

F

alias:
fabiocsilva:
Já pensou em utilizar bancos de dados NoSQL? Uma das aplicações mais comuns é o georeferenciamento, muitos deles já possuem suporte nativo.

Olha como é simples uma consulta no MongoDB, por exemplo:

db.places.find( { loc : { $near : [50,50] , $maxDistance : 5 } } ).limit(20)

fonte: http://www.mongodb.org/display/DOCS/Geospatial+Indexing

Cara valeu pela dica! Eu havia mesmo lido em algum lugar que o MongoDB suportava geo-indexação. É uma alternativa interessante, mas utiliza-lo teria muito impacto na minha aplicação, penso eu. Você concorda? Hoje já tenho as entidades todas mapeadas no Hibernate, usando o MySQL. Pra usar o Postgree (como eu tinha dito antes) seria só alterar a config do Hibernate…você acha que seria viável utilizar duas bases, mantendo o legado que tenho hoje e usando o MongoDB para armazenar os dados espaciais? Se bem que ainda que seja viável ia dar um trabalho pra implementar…

O seu exemplo me trouxe uma duvida que talvez possa ajudar, e tambem voltando a esse exemplo que o colega wagnerfrancisco havia mostrado:

No caso do MongoDB, essa distancia passada como filtro é em milhas? Prezado wagner, sabe dizer qual é a medida utilizada no Postgree?

Obrigado!

Quanto ao esforço de (re)implementação, não cabe a mim julgar. Em relação a unidade de medida, vai depender do tipo de coordenada. Se for convencional(considerando o espaço plano), a medida vai ser meramente ilustrativa. Se for utilizado o modelo esférico(que considera a curvatura da terra), o resultado será em milhas ou quilômetros. No link que passei anteriormente explica tudo.

A

Ops, me desculpe se o ofendi ou desrespeitei. Não foi de maneira alguma minha intenção. Novamente obrigado pela valiosa dica! Já tô montando uma POC aqui com um tal framework chamado Morphia e o MongoDB pra ver qual que é! :stuck_out_tongue:

A

Amigos, colocando a “solução” que encontrei…ao menos por enquanto, mas imagino que vou ficar com essa mesmo.

A extensão PostGis que o colega wagnerfrancisco citou (obrigado mesmo cara!) não está instalada no host que utilizo, eu teria que pagar um servidor dedicado para o banco (na real o valor nem é caro, mas não tô numas de gastar mais, hehe). Chorei, esperneei com o pessoal do host, mas não rolou, então deixei quieto o uso do PostgreeSQL por enquanto. Acredito que seria o mais adequado mas vai ficar pro futuro.

Quanto ao uso do MongoDB que o colega fabiocsilva citou, cara valeu pela dica fiz uns testes e gostei bastante, mas exigiria um esforço de implementação da minha parte que também não posso fazer agora, fora que eu nem entendi como seria a instalação da bagaça em um servidor hehe. Vou estudar mais o MongoDB antes de partir pro uso, mas recomendo!

Minha solução foi uma função para o MySQL que achei internet afora, que calcula a formula da Haversine para determinar a distancia entre dois pontos. Adaptei a função para calcular a distancia em quilometros, e beleza! tá funfando, só preciso fazer um teste com alguns milhares de registros pra testar melhor a performance. Mas foi isso, caso alguem necessite dessa funcionalidade no MySQL eu posto aqui o que fiz.

Valeu galera, obrigado a todos!

D

hum...

acho que essa função funciona também:

public static double distFrom(double lat1, double lng1, double lat2, double lng2) { 
    double earthRadius = 3958.75; 
    double dLat = Math.toRadians(lat2-lat1); 
    double dLng = Math.toRadians(lng2-lng1); 
    double a = Math.sin(dLat/2) * Math.sin(dLat/2) + 
               Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) * 
               Math.sin(dLng/2) * Math.sin(dLng/2); 
    double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); 
    double dist = earthRadius * c; 
 
    return dist; 
    }

em milhas, ai só converter para kilometros

A
douglaskd:
hum...

acho que essa função funciona também:

public static double distFrom(double lat1, double lng1, double lat2, double lng2) { 
    double earthRadius = 3958.75; 
    double dLat = Math.toRadians(lat2-lat1); 
    double dLng = Math.toRadians(lng2-lng1); 
    double a = Math.sin(dLat/2) * Math.sin(dLat/2) + 
               Math.cos(Math.toRadians(lat1)) * Math.cos(Math.toRadians(lat2)) * 
               Math.sin(dLng/2) * Math.sin(dLng/2); 
    double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); 
    double dist = earthRadius * c; 
 
    return dist; 
    }

em milhas, ai só converter para kilometros

Bacana, cara, valeu! É que no meu cenário eu precisava fazer esse calculo em uma query na base de dados, então não ia rolar ter a funcionalidade no código Java. Muito bom!

M

Aproveitando o tópico, alguém poderia dar uma ajuda fazendo favor?

Preciso também buscar quais são os pontos mais próximos de um determinado ponto, minha tabela está desse jeito:

id | nome | latitude | longitude

Em um tutorial, consegui criar a coluna geom da seguinte forma:

SELECT AddGeometryColumn( 'public', 'estacao', 'geom', 32661, 'POINT', 2 );

Depois, fiz o seguinte código para gerar os valores espaciais de cada ponto:

UPDATE estacao SET geom = ST_transform(ST_PointFromText('POINT(' || longitude || ' ' || latitude || ')',4269),32661);

No tutorial dizia que para fazer uma busca dos pontos mais próximos deveria ser desta maneira:

SELECT nome FROM estacao WHERE geom && ST_expand(ST_transform(ST_PointFromText('POINT(-49.2653819 -25.4244287)', 4269),32661), 16093) AND ST_distance(ST_transform(ST_PointFromText('POINT(-49.2653819 -25.4244287)', 4269),32661),geom) < 100;

Bom, ele retorna pra mim 28 estações que seriam os pontos mais próximos da lat e long: -49.2653819 -25.4244287, porem diminuindo o valor de 100 (que seria em metros) para 1, ele ainda me retorna as 28 estações.

O que estaria errado?

Se executo essa Sql também me retorna as 28 estações:

SELECT nome FROM estacao WHERE ST_distance(ST_transform(ST_PointFromText('POINT(-49.2653819 -25.4244287)', 4269),32661), geom) < 1;

Com a sql postada aqui no tópico, não retorna nada pra mim:

select * from estacao where ST_DWithin(ST_GeometryFromText('POINT(-49.2653819 -25.4244287)'), geom, 1000);

Aqui está um exemplo de como está algumas linhas da minha tabela:

id | latitude | longitude | nome | geom
182 | -25.4244287 | -49.2653819 | Estação tubo Aeroporto Afonso Pena | 0101000020957F00007332DC44E30169C188960590AA0165C1
183 | -25.4105796 | -49.2482570 | Estação tubo Agrárias | 0101000020957F00000029BE7406FE68C14FF4D0022E0265C1

Obrigado por enquanto :smiley:

J

A api maps 3.0 da google faz isso. Essa é a página da documentção:

https://developers.google.com/maps/documentation/distancematrix/

M

Obrigado pela resposta, fiz uns teste aqui e ficou bem legal utilizando a API do maps, porem lá fala que caso queira utilizar mais de um endereço de destino eu posso fazer isso, utilizando uma barra | para separar os pontos de destino.

Até ai blz, porém no meu caso, tenho 177 pontos cadastrados, caso queira comparar um ponto com qual ponto mais perto dele entre esses 177 pontos, eu teria que fazer um loop para isso certo? Fora isso, ainda teria que comparar qual é a menor distancia entre eles. Bom, não sei se esse seria o jeito de fazer com a API do maps, mas se for, acho esse processo bem repetitivo, por favor, me corrijam se estou errado.
A ideia de usar o Postgis era de ele mesmo retornar quais os pontos mais próximos dada uma medida, de forma mais simples.

Bom, se há uma outra maneira com a API do maps, ou então com o Postgis gostaria muito de saber.

Mais uma vez obrigado pela ajuda.

E

porra marlonfa vc acha q a API do postgis faz o calculo por mágica, claro q vai ter que saber as 177 posiçoes e saber a distancia de todas pra saber qual é a mais perto ou vc acha q ele simplesmente adivinha qual é o mais perto de primeira

M

eduJava:
porra marlonfa vc acha q a API do postgis faz o calculo por mágica, claro q vai ter que saber as 177 posiçoes e saber a distancia de todas pra saber qual é a mais perto ou vc acha q ele simplesmente adivinha qual é o mais perto de primeira

Vlw pela mensagem construtiva.

Obviamente sei que não existe a magica.

A

Reanimando o post, acho que a resposta encontra-se em outro post aqui do GUJ:

http://www.guj.com.br/39440-calcular-distancia-entre-coordenadas-geograficas

select (6371acos(cos(pi()(90 - (LAT_DESTINO))/180)* cos((90 - (LAT_ORIGEM))* pi()/180)+ sin((90 - (-LAT_DESTINO))* pi()/180)* si((90 - (LAT_ORIGEM))pi()/180) cos((LON_ORIGEM - (LON_DESTINO))*pi()/180)));

Não sei se usa ou não funções do PostGes, parece que são funções de álgebra.

Abraço.

Criado 22 de fevereiro de 2012
Ultima resposta 27 de jul. de 2015
Respostas 27
Participantes 11