Calcular ou consultar grandes distâncias circulares entre pontos de latitude e longitude usando a fórmula de Haversine (exemplos PHP, Python, MySQL, MSSQL)

Fórmula Haversine - Great Circle Distance - PHP, Python, MySQL

Este mês tenho programado bastante em PHP e MySQL no que diz respeito a GIS. Bisbilhotando na rede, eu realmente tive dificuldade em encontrar alguns dos Cálculos geográficos para encontrar a distância entre dois locais, então eu queria compartilhá-los aqui.

Mapa de voos da Europa com distância do grande círculo

A maneira simples de calcular uma distância entre dois pontos é usando a fórmula pitagórica para calcular a hipotenusa de um triângulo (A² + B² = C²). Isso é conhecido como Distância euclidiana.

Esse é um começo interessante, mas não se aplica à Geografia, pois a distância entre as linhas de latitude e longitude são distâncias não iguais separados. Conforme você se aproxima do equador, as linhas de latitude ficam mais distantes. Se você usar algum tipo de equação de triangulação simples, ela pode medir a distância com precisão em um local e terrivelmente errada no outro, por causa da curvatura da Terra.

Grande Distância do Círculo

As rotas que percorrem longas distâncias ao redor da Terra são conhecidas como Grande Distância do Círculo. Ou seja... a distância mais curta entre dois pontos em uma esfera é diferente dos pontos em um mapa plano. Combine isso com o fato de que as linhas de latitude e longitude não são equidistantes... e você terá um cálculo difícil.

Aqui está uma explicação em vídeo fantástica de como os Grandes Círculos funcionam.

A Fórmula de Haversine

A distância usando a curvatura da Terra é incorporada no Fórmula Haversine, que usa trigonometria para permitir a curvatura da Terra. Quando você está descobrindo a distância entre 2 lugares na Terra (em linha reta), uma linha reta é na verdade um arco.

Isso é aplicável em voos aéreos - você já olhou para o mapa real de voos e percebeu que eles são arqueados? Isso porque é mais curto voar em um arco entre dois pontos do que diretamente para o local.

PHP: Calcule a distância entre 2 pontos de latitude e longitude

Aqui está a fórmula PHP para calcular a distância entre dois pontos (juntamente com a conversão de milha x quilômetro) arredondada para duas casas decimais.

function getDistanceBetweenPointsNew($latitude1, $longitude1, $latitude2, $longitude2, $unit = 'miles') {
  $theta = $longitude1 - $longitude2; 
  $distance = (sin(deg2rad($latitude1)) * sin(deg2rad($latitude2))) + (cos(deg2rad($latitude1)) * cos(deg2rad($latitude2)) * cos(deg2rad($theta))); 
  $distance = acos($distance); 
  $distance = rad2deg($distance); 
  $distance = $distance * 60 * 1.1515; 
  switch($unit) { 
    case 'miles': 
      break; 
    case 'kilometers' : 
      $distance = $distance * 1.609344; 
  } 
  return (round($distance,2)); 
}

As variáveis ​​são:

  • $Latitude1 – uma variável para a latitude do seu primeiro local.
  • $Longitude1 – uma variável para a longitude do seu primeiro local
  • $Latitude2 – uma variável para a latitude do seu segundo local.
  • $Longitude2 – uma variável para a longitude do seu segundo local.
  • $ unidade – sendo o padrão milhas. Isso pode ser atualizado ou passado como quilômetros.

Python: Calcular distância entre 2 pontos de latitude e longitude

De qualquer forma, aqui está a fórmula Python para calcular a distância entre dois pontos (juntamente com a conversão de milha x quilômetro) arredondada para duas casas decimais. Crédito ao meu filho, Bill Karr, que é Cientista de Dados da AbrirINSIGHTS, para o código.

from numpy import sin, cos, arccos, pi, round

def rad2deg(radians):
    degrees = radians * 180 / pi
    return degrees

def deg2rad(degrees):
    radians = degrees * pi / 180
    return radians

def getDistanceBetweenPointsNew(latitude1, longitude1, latitude2, longitude2, unit = 'miles'):
    
    theta = longitude1 - longitude2
    
    distance = 60 * 1.1515 * rad2deg(
        arccos(
            (sin(deg2rad(latitude1)) * sin(deg2rad(latitude2))) + 
            (cos(deg2rad(latitude1)) * cos(deg2rad(latitude2)) * cos(deg2rad(theta)))
        )
    )
    
    if unit == 'miles':
        return round(distance, 2)
    if unit == 'kilometers':
        return round(distance * 1.609344, 2)

As variáveis ​​são:

  • latitude 1 – uma variável para o seu primeiro local latitude.
  • longitude1 – uma variável para o seu primeiro local longitude
  • latitude 2 – uma variável para o seu segundo local latitude.
  • longitude2 – uma variável para o seu segundo local longitude.
  • unidade – sendo o padrão milhas. Isso pode ser atualizado ou passado como quilômetros.

MySQL: recuperando todos os registros dentro de um intervalo calculando a distância em milhas usando latitude e longitude

Também é possível usar o SQL para fazer um cálculo para encontrar todos os registros dentro de uma distância específica. Neste exemplo, vou consultar MyTable no MySQL para encontrar todos os registros que são menores ou iguais à variável $ distance (em milhas) para minha localização em $ latitude e $ longitude:

A consulta para recuperar todos os registros em um determinado distância calculando a distância em milhas entre dois pontos de latitude e longitude são:

$query = "SELECT *, (((acos(sin((".$latitude."*pi()/180)) * sin((`latitude`*pi()/180)) + cos((".$latitude."*pi()/180)) * cos((`latitude`*pi()/180)) * cos(((".$longitude."- `longitude`)*pi()/180)))) * 180/pi()) * 60 * 1.1515) as distance FROM `table` WHERE distance <= ".$distance."

Você precisará personalizar isto:

  • $ longitude - esta é uma variável PHP onde estou passando a longitude do ponto.
  • $ latitude - esta é uma variável PHP onde estou passando a longitude do ponto.
  • $ distância - esta é a distância que você gostaria de encontrar todos os registros menores ou iguais.
  • mesa - esta é a mesa ... você vai querer substituir isso pelo nome da sua mesa.
  • latitude - este é o campo de sua latitude.
  • longitude - este é o campo de sua longitude.

MySQL: recuperando todos os registros dentro de um intervalo calculando a distância em quilômetros usando latitude e longitude

E aqui está a consulta SQL usando quilômetros no MySQL:

$query = "SELECT *, (((acos(sin((".$latitude."*pi()/180)) * sin((`latitude`*pi()/180)) + cos((".$latitude."*pi()/180)) * cos((`latitude`*pi()/180)) * cos(((".$longitude."- `longitude`) * pi()/180)))) * 180/pi()) * 60 * 1.1515 * 1.609344) as distance FROM `table` WHERE distance <= ".$distance."

Você precisará personalizar isto:

  • $ longitude - esta é uma variável PHP onde estou passando a longitude do ponto.
  • $ latitude - esta é uma variável PHP onde estou passando a longitude do ponto.
  • $ distância - esta é a distância que você gostaria de encontrar todos os registros menores ou iguais.
  • mesa - esta é a mesa ... você vai querer substituir isso pelo nome da sua mesa.
  • latitude - este é o campo de sua latitude.
  • longitude - este é o campo de sua longitude.

Usei esse código em uma plataforma de mapeamento empresarial que utilizamos para uma loja de varejo com mais de 1,000 locais na América do Norte e funcionou perfeitamente.

Distância geográfica do Microsoft SQL Server: STDistance

Se você estiver utilizando o Microsoft SQL Server, eles oferecem sua própria função, Distância ST para calcular a distância entre dois pontos usando o tipo de dados Geography.

DECLARE @g geography;  
DECLARE @h geography;  
SET @g = geography::STGeomFromText('LINESTRING(-122.360 47.656, -122.343 47.656)', 4326);  
SET @h = geography::STGeomFromText('POINT(-122.34900 47.65100)', 4326);  
SELECT @g.STDistance(@h);  

Gorjeta para Manash Sahoo, vice-presidente e arquiteto da Highbridge.

77 Comentários

  1. 1

    Muito obrigado por compartilhar. Este foi um trabalho fácil de copiar e colar e funciona muito bem. Você me economizou muito tempo.
    FYI para quem está portando para C:
    double deg2rad(double deg) { return deg*(3.14159265358979323846/180.0); }

  2. 2

    Muito legal o post – funcionou muito bem – só tive que mudar o nome da mesa que segurava o lat-long. Ele funciona bem rápido para... Eu tenho um número razoavelmente pequeno de lat-longs (< 400), mas acho que isso seria bom. Site legal também – acabei de adicioná-lo à minha conta del.icio.us e vou verificar regularmente.

  3. 4
  4. 5

    Procurei o dia inteiro por cálculos de distância e encontrei o algoritmo harversine, graças a você por dar o exemplo de como colocá-lo em uma instrução sql. Obrigado e um abraço, Daniel

  5. 8

    eu acho que seu SQL precisa de uma declaração tendo.
    em vez de WHERE distance <= $distance você pode precisar
    use HAVING distance <= $distance

    caso contrário, obrigado por me poupar um monte de tempo e energia.

  6. 10
  7. 11
  8. 12

    Muito obrigado por compartilhar este código. Isso me economizou muito tempo de desenvolvimento. Além disso, obrigado aos seus leitores por apontarem que uma instrução HAVING é necessária para o MySQL 5.x. Muito útil.

  9. 14
  10. 15
  11. 16

    Eu também descobri que WHERE não funcionou para mim. Mudei para HAVING e tudo funciona perfeitamente. No começo eu não li os comentários e reescrevi usando uma seleção aninhada. Ambos funcionarão muito bem.

  12. 17
  13. 18

    Incrivelmente útil, muito obrigado! Eu estava tendo alguns problemas com o novo “HAVING”, em vez de “WHERE”, mas depois de ler os comentários aqui (depois de cerca de meia hora rangendo os dentes de frustração =P), consegui que funcionasse bem. Obrigado ^_^

  14. 19
  15. 20

    Tenha em mente que uma instrução select como essa será muito computacionalmente intensa e, portanto, lenta. Se você tiver muitas dessas consultas, isso pode atrapalhar as coisas rapidamente.

    Uma abordagem muito menos intensa é executar uma primeira seleção (crua) usando uma área QUADRADA definida por uma distância calculada, ou seja, “selecione * de tablename onde latitude entre lat1 e lat2 e longitude entre lon1 e lon2”. lat1 = targetlatitude – latdiff, lat2 = targetlatitude + latdiff, semelhante a lon. latdiff ~= distance / 111 (para km), ou distance/69 para milhas, pois 1 grau de latitude é ~ 111 km (pequena variação, pois a terra é ligeiramente oval, mas suficiente para esse fim). londiff = distance / (abs(cos(deg2rad(latitude)))*111)) — ou 69 para milhas (você pode pegar um quadrado um pouco maior para levar em conta as variações). Então pegue o resultado disso e alimente-o na seleção radial. Apenas não se esqueça de considerar as coordenadas fora dos limites - ou seja, o intervalo de longitude aceitável é -180 a +180 e o intervalo de latitude aceitável é -90 a +90 - caso seu latdiff ou londiff esteja fora desse intervalo . Observe que, na maioria dos casos, isso pode não ser aplicável, pois afeta apenas os cálculos ao longo de uma linha que atravessa o oceano pacífico de um pólo a outro, embora intercepte parte de chukotka e parte do alasca.

    O que conseguimos com isso é uma redução significativa no número de pontos contra os quais você faz esse cálculo. Se você tiver um milhão de pontos globais no banco de dados distribuídos aproximadamente uniformemente e quiser pesquisar dentro de 100 km, sua primeira pesquisa (rápida) será de uma área de 10000 km20 e provavelmente produzirá cerca de 500 resultados (com base na distribuição uniforme em um área de superfície de cerca de 20 milhões de quilômetros quadrados), o que significa que você executa o cálculo de distância complexa XNUMX vezes para essa consulta em vez de um milhão de vezes.

    • 21

      Pequeno erro no exemplo… isso seria para dentro de 50 km (não 100) já que estamos olhando para o “raio” do nosso… quadrado.

      • 22

        Conselho fantástico! Na verdade, trabalhei com um desenvolvedor que escreveu uma função que puxava o quadrado interno e, em seguida, uma função recursiva que criava 'quadrados' ao redor do perímetro para incluir e excluir os pontos restantes. O resultado foi incrivelmente rápido – ele podia avaliar milhões de pontos em microssegundos.

        Minha abordagem acima é definitivamente 'crua', mas capaz. Obrigado novamente!

        • 23

          Douglas,

          Eu tenho tentado usar mysql e php para avaliar se um ponto de lat long está dentro de um polígono. Você sabe se seu amigo desenvolvedor publicou algum exemplo de como realizar essa tarefa. Ou você conhece algum bom exemplo. Desde já, obrigado.

  16. 24

    Olá a todos, esta é a minha instrução SQL de teste:

    SELECT DISTINCT area_id, (
    (
    (
    acos( sin( ( 13.65 * pi( ) /180 ) ) * sin( (
    `lat_dec` * pi( ) /180 ) ) + cos( ( 13.65 * pi( ) /180 ) ) * cos( (
    `lat_dec` * pi( ) /180 )
    ) * cos( (
    ( 51.02 - `lon_dec` ) * pi( ) /180 )
    )
    )
    ) *180 / pi( )
    ) *60 * 1.1515 * 1.609344
    ) AS distance
    FROM `post_codes` WHERE distance <= 50

    e o Mysql está me dizendo que a distância, não existe como coluna, posso usar order by, posso fazer sem WHERE, e funciona, mas não com ele…

  17. 26

    Isso é ótimo, mas é como os pássaros voam. Seria ótimo tentar incorporar a API do google maps para isso de alguma forma (talvez usando estradas etc.) Só para dar uma ideia usando um meio de transporte diferente. Ainda tenho que fazer uma função de recozimento simulada em PHP que seja capaz de oferecer uma solução eficiente para o problema do caixeiro viajante. Mas acho que posso reutilizar parte do seu código para fazer isso.

  18. 27
  19. 28

    Bom artigo! Encontrei muitos artigos descrevendo como calcular a distância entre dois pontos, mas estava realmente procurando o trecho SQL.

  20. 29
  21. 30
  22. 31
  23. 32
  24. 36

    2 dias de pesquisa para finalmente encontrar esta página que resolve o meu problema. Parece que é melhor eu pegar meu WolframAlpha e melhorar minha matemática. A mudança de WHERE para HAVING tem meu script funcionando. OBRIGADA

  25. 37
    • 38

      Obrigado Georgi. Continuei recebendo a coluna 'distância' não encontrada. Uma vez que mudei o WHERE para HAVING funcionou como um encanto!

  26. 39

    Eu gostaria que esta fosse a primeira página que eu encontrei sobre isso. Depois de tentar muitos comandos diferentes, este foi o único a funcionar corretamente e com alterações mínimas necessárias para caber no meu próprio banco de dados.
    Muito obrigado!

  27. 40

    Eu gostaria que esta fosse a primeira página que eu encontrei sobre isso. Depois de tentar muitos comandos diferentes, este foi o único a funcionar corretamente e com alterações mínimas necessárias para caber no meu próprio banco de dados.
    Muito obrigado!

  28. 41
  29. 42
  30. 43
  31. 45
  32. 46
  33. 47

    Eu sei que essa fórmula funciona, mas não consigo ver onde o raio da Terra é levado em consideração. Alguém pode me iluminar por favor ?

  34. 49
  35. 50
  36. 52

    Obrigado Douglas, a consulta SQL é exatamente o que eu precisava, e pensei que teria que escrevê-la eu mesmo. Você me salvou de possivelmente horas de curva de aprendizado de latitude e longitude!

  37. 53
  38. 55
  39. 56
  40. 58

    obrigado por postar este artigo útil,  
    mas por algum motivo eu gostaria de perguntar
    como obter a distância entre as coordenadas dentro do mysql db e as coordenadas inseridas no php pelo usuário?
    para descrever mais claramente:
    1. o usuário deve inserir [id] para selecionar os dados especificados do banco de dados e as coordenadas do próprio usuário
    2. o arquivo php obtém os dados de destino (coords) usando [id] e calcula a distância entre o usuário e o ponto de destino

    ou pode simplesmente obter distância do código abaixo?

    $qry = “SELECT *,(((acos(sin((“.$latitude.”*pi()/180))) * sin((`Latitude`*pi()/180))+cos((“. $latitude.”*pi()/180)) * cos((`Latitude`*pi()/180)) * cos(((“.$longitude.”- `Longitude`)*pi()/180) )))*180/pi())*60*1.1515*1.609344) as distance FROM `MyTable` WHERE distance >= “.$distance.” >>>>posso “tirar” a distância daqui?
    obrigado novamente,
    Timmy S

  41. 60

    ok, tudo que eu tentei não está funcionando. Quer dizer, o que eu tenho funciona, mas as distâncias estão longe.

    Alguém poderia ver o que está errado com este código?

    if(isset($_POST['submitted'])){ $z = $_POST['zipcode']; $r = $_POST['raio']; echo “Resultados para “.$z; $sql = mysql_query(“SELECT DISTINCT m.zipcode, m.MktName,m.LocAddSt,m.LocAddCity,m.LocAddState,m.x1,m.y1,m.verified,z1.lat,z2.lon,z1. city,z1.state FROM mrk m, zip z1, zip z2 ONDE m.zipcode = z1.zipcode AND z2.zipcode = $z AND (3963 * acos(truncate( sin( z2.lat / 57.2958 ) * sin( m. y1 / 57.2958 ) + cos( z2.lat / 57.2958 ) * cos( m.y1 / 57.2958 ) * cos( m.x1 / 57.2958 – z2.lon / 57.2958 ), 8 ) ) ) <= $r ") ou morrer (mysql_error()); while($row = mysql_fetch_array( $sql)) { $store1 = $row['MktName']."”; $loja = $linha['LocAddSt'].””; $store .= $row['LocAddCity']”, “.$row['LocAddState'].” “.$row['código postal']; $latitude1 = $linha['lat']; $longitude1 = $linha['lon']; $latitude2 = $linha['a1']; $longitude2 = $linha['x1']; $cidade = $linha['cidade']; $estado = $linha['estado']; $dis = getnew($latitude1, $longitude1, $latitude2, $longitude2, $unit = 'Mi'); // $dis = distancia($lat1, $lon1, $lat2, $lon2); $verificado = $linha['verificado']; if($verificado == '1'){ echo “”; echo “.$loja.””; echo $dis . " a milhas de distância"; eco “”; } else { echo “”.$loja.””; echo $dis . " a milhas de distância"; eco “”; } }}

    meu código functions.php
    function getnew($latitude1, $longitude1, $latitude2, $longitude2, $unit = 'Mi') { $theta = $longitude1 – $longitude2; $distância = (sin(deg2rad($latitude1)) * sin(deg2rad($latitude2))) + (cos(deg2rad($latitude1)) * cos(deg2rad($latitude2)) * cos(deg2rad($theta)) ); $distância = acos($distância); $distância = rad2graus($distância); $distância = $distância * 60 * 1.1515; switch($unit) { case 'Mi': break; case 'Km' : $distância = $distância * 1.609344; } return (round($distância,2)); }

    Obrigado antecipadamente

  42. 61
  43. 62

    Oi Douglas, ótimo artigo. Achei muito interessante sua explicação dos conceitos geográficos e do código. Minha única sugestão seria espaçar e recuar o código para exibição (como Stackoverflow, por exemplo). Entendo que você queira economizar espaço, mas o espaçamento / recuo de código convencional tornaria muito mais fácil para mim, como programador, ler e dissecar. De qualquer forma, isso é uma coisa pequena. Continue com o ótimo trabalho.

  44. 64
  45. 65

    aqui enquanto estiver usando com a função estamos obtendo um tipo de distância .. enquanto estiver usando a consulta seu outro tipo de distância

  46. 66
  47. 67
  48. 68
  49. 69
  50. 70

    parece mais rápido (mysql 5.9) usar o dobro da fórmula no select e onde:
    $formula = “(((acos(sin((“.$latitude.”*pi()/180))) * sin((`Latitude`*pi()/180))+cos((“.$latitude. ”*pi()/180)) * cos((`Latitude`*pi()/180)) * cos(((“.$longitude.”- `Longitude`)*pi()/180))))) *180/pi())*60*1.1515*1.609344)”;
    $sql = 'SELECT *, '.$formula.' como distância da tabela WHERE '..$formula.' <= '.$distância;

  51. 71
  52. 72

    Muito obrigado por cisalhar este artigo.é muito útil.
    O PHP foi inicialmente criado como uma plataforma de script simples chamada “Personal Home Page”. Atualmente o PHP (abreviação de Hypertext Preprocessor) é uma alternativa à tecnologia Active Server Pages (ASP) da Microsoft.

    PHP é uma linguagem do lado do servidor de código aberto que é usada para criar páginas da web dinâmicas. Pode ser embutido em HTML. O PHP geralmente é usado em conjunto com um banco de dados MySQL em servidores web Linux/UNIX. É provavelmente a linguagem de script mais popular.

  53. 73

    Encontrei a solução acima que não está funcionando corretamente.
    Eu preciso mudar para:

    $qqq = “SELECT *,(((acos(sin((“.$latitude.”*pi()/180))) * sin((`latt`*pi()/180))+cos((” . $latitude . “*pi()/180)) * cos((`latt`*pi()/180)) * cos(((” . $longitude . “- `longt`)*pi()/180) )))*180/pi())*60*1.1515) como distância FROM `register` “;

  54. 75
  55. 76

    Olá, por favor, eu realmente preciso de sua ajuda sobre isso.

    Fiz um pedido de obtenção ao meu servidor web http://localhost:8000/users/findusers/53.47792/-2.23389/20/
    53.47792 = $ latitude
    -2.23389 = $longitude
    e 20 = a distância que eu quero recuperar

    No entanto, usando sua fórmula, ele recupera todas as linhas no meu banco de dados

    $resultados = DB::select( DB::raw(“SELECT *, (((acos(sin((“.$latitude.”*pi()/180))) * sin((lat*pi()/180 ))+cos((“.$latitude.”*pi()/180)) * cos((lat*pi()/180)) * cos(((“.$longitude.”- lng)*pi( )/180))))*180/pi())*60*1.1515*1.609344) como distância FROM marcadores HAVING distance >= “.$distance ));

    [{“id”:1,”name”:”Frankie Johnnie & Luigo Too”,”address”:”939 W El Camino Real, Mountain View, CA”,”lat”:37.386337280273,”lng”:-122.08582305908, ”distance”:16079.294719663},{“id”:2,”name”:”Amici's East Coast Pizzeria”,”address”:”790 Castro St, Mountain View, CA”,”lat”:37.387138366699,”lng”: -122.08323669434,"distância":16079.175940152},{"id":3,"nome":"Kapp's Pizza Bar & Grill","endereço":"191 Castro St, Mountain View, CA","lat":37.393886566162, ”lng”:-122.07891845703,”distance”:16078.381373826},{“id”:4,”name”:”Round Table Pizza: Mountain View”,”address”:”570 N Shoreline Blvd, Mountain View, CA”, ”lat”:37.402652740479,”lng”:-122.07935333252,”distance”:16077.420540582},{“id”:5,”name”:”Tony & Alba's Pizza & Pasta”,”endereço”:”619 Escuela Ave, Mountain View, CA”,”lat”:37.394012451172,”lng”:-122.09552764893,”distance”:16078.563225154},{“id”:6,”name”:”Oregano's Wood-Fired Pizza”,”address”:”4546 El Camino Real, Los Altos, CA”,”lat”:37.401725769043,”lng”:-122.11464691162,”distance”:16077.937560795},{“ id”:7,”name”:”The bars and grills”,”address”:”24 Whiteley Street, Manchester”,”lat”:53.485118865967,”lng”:-2.1828699111938,”distance”:8038.7620112314}]

    Eu quero recuperar apenas linhas com 20 milhas, mas traz todas as linhas. Por favor, o que estou fazendo de errado

  56. 77

    Estou procurando uma consulta semelhante, mas intensificou um pouco - em resumo, isso é agrupar todas as coordenadas dentro de 2 milhas de cada coordenada e contar quantas coordenadas em cada grupo e produzir apenas um grupo que tenha mais coordenadas - mesmo se você tem mais de um grupo entre os grupos que possuem o maior número de coordenadas - basta emitir o grupo aleatório dos grupos com o mesmo número maior -

O que você acha?

Este site usa o Akismet para reduzir o spam. Saiba como seus dados de comentário são processados.