Calcula ou consulta a distancia dun círculo grande entre puntos de latitude e lonxitude usando a fórmula Havesine (Exemplos PHP, Python, MySQL, MSSQL)

Fórmula Havesine - Gran Distancia Círculo - PHP, Python, MySQL

Este mes estiven programando bastante en PHP e MySQL con respecto aos SIX. Paseando pola rede, de verdade custoume atopar algúns dos Cálculos xeográficos para atopar a distancia entre dous lugares, así que quería compartilos aquí.

Mapa de voos Europa con gran círculo de distancia

A forma sinxela de calcular a distancia entre dous puntos é empregando a fórmula pitagórica para calcular a hipotenusa dun triángulo (A² + B² = C²). Isto coñécese como Distancia euclidiana.

É un comezo interesante, pero non se aplica a Xeografía xa que a distancia entre liñas de latitude e lonxitude é distancias non iguais aparte. A medida que te achegas ao ecuador, as liñas de latitude afástanse. Se empregas algún tipo de ecuación de triangulación sinxela, pode medir a distancia con precisión nun lugar e terriblemente mal no outro, debido á curvatura da Terra.

Distancia Gran Círculo

As rutas que se percorren longas distancias ao redor da Terra coñécense como Distancia Gran Círculo. É dicir... a distancia máis curta entre dous puntos dunha esfera é diferente dos puntos dun mapa plano. Combínao co feito de que as liñas de latitude e lonxitude non son equidistantes... e tes un cálculo difícil.

Aquí tes unha fantástica explicación en vídeo de como funcionan os grandes círculos.

A Fórmula Haversine

A distancia usando a curvatura da Terra incorpórase ao Fórmula Haversine, que usa a trigonometría para permitir a curvatura da terra. Cando atopas a distancia entre 2 lugares da terra (en liña recta), a liña recta é realmente un arco.

Isto é aplicable no voo aéreo. Algunha vez mirou o mapa real dos voos e notou que están arqueados? É porque é máis curto voar nun arco entre dous puntos que directamente á localización.

PHP: calcular a distancia entre 2 puntos de latitude e lonxitude

Aquí tes a fórmula PHP para calcular a distancia entre dous puntos (xunto coa conversión de millas a quilómetro) redondeada a dous 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 variables son:

  • $Latitude1 – unha variable para a latitude da súa primeira localización.
  • $Lonxitude1 – unha variable para a lonxitude da súa primeira localización
  • $Latitude2 – unha variable para a latitude da súa segunda localización.
  • $Lonxitude2 – unha variable para a lonxitude da súa segunda localización.
  • $unidade - o ser por defecto millas. Isto pódese actualizar ou pasar como quilómetros.

Python: calcula a distancia entre 2 puntos de latitude e lonxitude

De todos os xeitos, aquí tes a fórmula de Python para calcular a distancia entre dous puntos (xunto coa conversión de millas a quilómetros) redondeada a dous decimais. Crédito para o meu fillo, Bill Karr, que é científico de datos OpenINSIGHTS, 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 variables son:

  • latitude 1 – unha variable para a súa primeira localización latitud.
  • lonxitude 1 – unha variable para a súa primeira localización lonxitude
  • latitude 2 – unha variable para a súa segunda localización latitud.
  • lonxitude 2 – unha variable para a súa segunda localización lonxitude.
  • unidade - o ser por defecto millas. Isto pódese actualizar ou pasar como quilómetros.

MySQL: recuperando todos os rexistros dentro dun intervalo calculando a distancia en millas usando latitude e lonxitude

Tamén é posible usar SQL para facer un cálculo para atopar todos os rexistros a unha distancia específica. Neste exemplo, vou consultar MyTable en MySQL para atopar todos os rexistros que sexan menores ou iguais a $ distancia variable (en millas) ata a miña localización a $ latitude e $ lonxitude:

A consulta para recuperar todos os rexistros nun determinado distancia ao calcular a distancia en millas entre dous puntos de latitude e lonxitude son:

$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."

Deberás personalizar isto:

  • $ lonxitude - Esta é unha variable PHP onde estou pasando a lonxitude do punto.
  • $ latitude - Esta é unha variable PHP onde estou pasando a lonxitude do punto.
  • $ distancia - esta é a distancia á que che gustaría atopar todos os rexistros menos ou iguais.
  • táboa - esta é a táboa ... quererá substituíla polo nome da táboa.
  • latitud - este é o campo da súa latitude.
  • lonxitude - este é o campo da túa lonxitude.

MySQL: recuperando todos os rexistros dentro dun intervalo calculando a distancia en quilómetros usando a latitude e a lonxitude

E aquí está a consulta SQL usando quilómetros en 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."

Deberás personalizar isto:

  • $ lonxitude - Esta é unha variable PHP onde estou pasando a lonxitude do punto.
  • $ latitude - Esta é unha variable PHP onde estou pasando a lonxitude do punto.
  • $ distancia - esta é a distancia á que che gustaría atopar todos os rexistros menos ou iguais.
  • táboa - esta é a táboa ... quererá substituíla polo nome da táboa.
  • latitud - este é o campo da súa latitude.
  • lonxitude - este é o campo da túa lonxitude.

Utilicei este código nunha plataforma de mapeo empresarial que empregamos para unha tenda de venda polo miúdo con máis de 1,000 locais en toda América do Norte e funcionou moi ben.

Distancia xeográfica de Microsoft SQL Server: STDistance

Se estás utilizando Microsoft SQL Server, ofrecen a súa propia función, Distancia ST para calcular a distancia entre dous puntos utilizando o tipo de datos Xeography.

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);  

Consello de sombreiro para Manash Sahoo, vicepresidente e arquitecto de Highbridge.

77 Comentarios

  1. 1

    Moitas grazas por compartilo. Este foi un traballo de copiar e pegar sinxelo e funciona moi ben. Aforrachesme moito tempo.
    FYI para quen realice a portabilidade a C:
    double deg2rad (double deg) { devolver graos*(3.14159265358979323846/180.0); }

  2. 2

    Unha publicación moi bonita -funcionou moi ben- só tiven que cambiar o nome da mesa que sostén o lat-long. Funciona bastante rápido para.. Teño un número razoablemente pequeno de lat-longs (< 400) pero creo que isto escalaría ben. Sitio agradable tamén: acabo de engadilo á miña conta del.icio.us e volverei comprobar regularmente.

  3. 4
  4. 5
  5. 8

    Creo que o teu SQL necesita unha declaración have.
    en lugar de WHERE distancia <= $distancia pode que necesites
    use HAVING distance <= $distancia

    senón grazas por aforrarme un montón de tempo e enerxía.

  6. 10
  7. 11
  8. 12

    Moitas grazas por compartir este código. Aforroume moito tempo de desenvolvemento. Ademais, grazas aos teus lectores por sinalar que unha instrución HAVING é necesaria para MySQL 5.x. Moi útil.

  9. 14
  10. 15
  11. 16

    Tamén descubrín que WHERE non funcionaba para min. Cambiémolo a HAVING e todo funciona perfectamente. Ao principio non lin os comentarios e os reescribín usando unha selección aniñada. Ambos funcionarán ben.

  12. 17
  13. 18

    Incriblemente útil, moitas grazas! Estaba tendo algúns problemas co novo "HAVING", en lugar de "WHERE", pero unha vez que lin os comentarios aquí (despois de aproximadamente media hora de rechinar os dentes con frustración =P), conseguín que funcionase ben. Grazas ^_^

  14. 19
  15. 20

    Teña en conta que unha declaración selecta como esa será moi intensa computacionalmente e, polo tanto, lenta. Se tes moitas desas consultas, podes atascar as cousas con bastante rapidez.

    Un enfoque moito menos intenso é realizar unha primeira selección (cruda) utilizando unha área CUADRADA definida por unha distancia calculada, é dicir, "select * from tablename onde a latitude entre lat1 e lat2 e a lonxitude entre lon1 e lon2". lat1 = targetlatitude – latdiff, lat2 = targetlatitude + latdiff, semellante con lon. latdiff ~= distancia / 111 (para km), ou distancia / 69 para millas xa que 1 grao de latitude é ~ 111 km (lixeira variación xa que a terra é lixeiramente oval, pero suficiente para este fin). londiff = distancia / (abs(cos(deg2rad(latitud))*111)) — ou 69 por millas (en realidade, pode tomar un cadrado un pouco máis grande para ter en conta as variacións). A continuación, tome o resultado e introdúceo na selección radial. Non esquezas ter en conta as coordenadas fóra dos límites, é dicir, o rango de lonxitude aceptable é de -180 a +180 e o rango de latitude aceptable é de -90 a +90, no caso de que o teu latdiff ou londiff estea fóra deste intervalo. . Teña en conta que na maioría dos casos isto pode non ser aplicable xa que só afecta aos cálculos sobre unha liña que atravesa o océano Pacífico de polo a polo, aínda que si cruza parte de chukotka e parte de alaska.

    O que conseguimos con isto é unha redución significativa no número de puntos contra os que fai este cálculo. Se tes un millón de puntos globais na base de datos distribuídos aproximadamente de xeito uniforme e queres buscar dentro de 100 km, a túa primeira busca (rápida) é dunha área de 10000 km² e probablemente dará uns 20 resultados (baseados nunha distribución uniforme nun superficie duns 500 M² de km²), o que significa que executa o cálculo de distancia complexo 20 veces para esta consulta en lugar de un millón de veces.

    • 21
      • 22

        Fantástico consello! De feito, traballei cun programador que escribiu unha función que tiraba do cadrado interior e despois unha función recursiva que facía "cadrados" ao redor do perímetro para incluír e excluír os puntos restantes. O resultado foi un resultado incriblemente rápido: podía avaliar millóns de puntos en microsegundos.

        O meu enfoque anterior é definitivamente "bruto" pero capaz. Grazas de novo!

        • 23

          Doug,

          Estiven intentando usar mysql e php para avaliar se un punto longo lat está dentro dun polígono. Sabes se o teu amigo desenvolvedor publicou algún exemplo sobre como realizar esta tarefa. Ou coñeces algún bo exemplo. Grazas de antemán.

  16. 24

    Ola a todos, esta é a miña declaración SQL de proba:

    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 Mysql dime que a distancia, non existe como columna, podo usar orde por, podo facelo sen DONDE e funciona, pero non con ela...

  17. 26

    Isto é xenial, pero é como voan os paxaros. Sería xenial tentar incorporar a API de Google Maps a isto dalgún xeito (quizais usando estradas, etc.) Só para dar unha idea usando un medio de transporte diferente. Aínda teño que facer unha función de recocido simulado en PHP que poida ofrecer unha solución eficiente ao problema do vendedor ambulante. Pero creo que pode ser capaz de reutilizar parte do teu código para facelo.

  18. 27
  19. 28

    Bo artigo! Atopei moitos artigos que describen como calcular a distancia entre dous puntos, pero realmente estaba a buscar o fragmento de SQL.

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

    2 días de investigación para atopar por fin esta páxina que soluciona o meu problema. Parece que é mellor que saque o meu WolframAlpha e repase as miñas matemáticas. O cambio de WHERE a HAVING ten o meu guión en funcionamento. GRAZAS

  25. 37
  26. 39

    Gustaríame que esta fose a primeira páxina que atopei sobre isto. Despois de probar moitos comandos diferentes, este foi o único que funcionou correctamente, e con cambios mínimos necesarios para adaptarse á miña propia base de datos.
    Moitas grazas!

  27. 40

    Gustaríame que esta fose a primeira páxina que atopei sobre isto. Despois de probar moitos comandos diferentes, este foi o único que funcionou correctamente, e con cambios mínimos necesarios para adaptarse á miña propia base de datos.
    Moitas grazas!

  28. 41
  29. 42
  30. 43
  31. 45
  32. 46
  33. 47
  34. 49
  35. 50
  36. 52
  37. 53
  38. 55
  39. 56
  40. 58

    grazas por publicar este útil artigo,  
    pero por algún motivo gustaríame preguntar
    como obter a distancia entre as coordenadas dentro de mysql db e as coordenadas inseridas en php polo usuario?
    para describir con máis claridade:
    1. o usuario ten que inserir [id] para seleccionar os datos especificados de db e as coordenadas do propio usuario
    2. O ficheiro php obtén os datos de destino (coords) usando [id] e despois calcula a distancia entre o usuario e o punto de destino

    ou simplemente podes distanciar do seguinte código?

    $qry = “SELECT *,(((acos(sin((“.$latitude.”*pi()/180)) * sin((`Latitude`*pi()/180))+cos((“. $latitude.”*pi()/180)) * cos((`Latitude`*pi()/180)) * cos(((“.$lonxitude.”- `Lonxitude`)*pi()/180) )))*180/pi())*60*1.1515*1.609344) como distancia FROM `MyTable` WHERE distancia >= “.$distancia”. >>>>podo “sacar” a distancia dende aquí?
    grazas de novo,
    Timmy S

  41. 60

    ok, todo o que probei non funciona. Quero dicir, o que teño funciona, pero as distancias están moi afastadas.

    Alguén podería ver o que está mal con este código?

    if(isset($_POST['enviado'])){ $z = $_POST['código postal']; $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. cidade,z1.estado FROM mrk m, zip z1, zip z2 WHERE 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 ) ) ) ou <= $e ") (mysql_error()); while($fila = mysql_fetch_array($sql)) { $store1 = $fila['MktName']."”; $store = $row['LocAddSt'].””; $store .= $row['LocAddCity'].”, “.$row['LocAddState']”. “.$fila['código postal']; $latitude1 = $fila['lat']; $longitude1 = $fila['lon']; $latitude2 = $fila ['y1']; $lonxitude2 = $fila['x1']; $cidade = $fila['cidade']; $estado = $fila['estado']; $dis = getnew($latitude1, $lonxitude1, $latitude2, $lonxitude2, $unidade = 'Mi'); // $dis = distancia($lat1, $lon1, $lat2, $lon2); $verificado = $fila['verificado']; if($verificado == '1'){ echo “”; echo “”.$tenda.””; echo $dis . ” milla(s) de distancia”; eco ""; } else { echo “”.$tenda.””; echo $dis . ” milla(s) de distancia”; eco ""; } }}

    o meu código functions.php
    función getnew($latitude1, $lonxitude1, $latitude2, $lonxitude2, $unidade = 'Mi') { $theta = $lonxitude1 – $lonxitude2; $distancia = (sin(deg2rad($latitude1)) * sin(deg2rad($latitude2))) + (cos(deg2rad($latitude1)) * cos(deg2rad($latitude2)) * cos(deg2rad($theta)) ); $distancia = acos($distancia); $distancia = rad2deg($distancia); $distancia = $distancia * 60 * 1.1515; switch($unidade) { case 'Mi': break; caso 'Km' : $distancia = $distancia * 1.609344; } retorno (redondo($distancia,2)); }

    Grazas anticipadamente

  42. 61
  43. 62

    Ola Douglas, excelente artigo. Pareceume moi interesante a túa explicación dos conceptos xeográficos e do código. A miña única suxestión sería espaciar e sangrar o código para mostrar (como Stackoverflow, por exemplo). Entendo que queres aforrar espazo, pero o espaciado/sangrado de código convencional facilitaríame moito, como programador, a lectura e a disección. De todos os xeitos, iso é unha pequena cousa. Sigue co gran traballo.

  44. 64
  45. 65
  46. 66
  47. 67
  48. 68
  49. 69
  50. 70

    Parece máis rápido (mysql 5.9) usar o dobre da fórmula na selección e onde:
    $fórmula = “(((acos(sin((“.$latitude.”*pi()/180)) * sin((`Latitude`*pi()/180))+cos((“.$latitude. ”*pi()/180)) * cos((`Latitude`*pi()/180)) * cos(((“.$lonxitude.”- `Lonxitude`)*pi()/180)))) *180/pi())*60*1.1515*1.609344)”;
    $sql = 'SELECT *, '.$fórmula.' como distancia FROM a táboa WHERE '..$formula.' <= '.$distancia;

  51. 71
  52. 72

    Moitas grazas por escoitar este artigo. É moi útil.
    PHP creouse nun principio como unha plataforma de script sinxela chamada "Páxina de inicio persoal". Hoxe en día PHP (a abreviatura de Hypertext Preprocessor) é unha alternativa á tecnoloxía ASP (Active Server Pages) de Microsoft.

    PHP é unha linguaxe de código aberto no lado do servidor que se usa para crear páxinas web dinámicas. Pódese incrustar en HTML. PHP úsase normalmente xunto cunha base de datos MySQL en servidores web Linux/UNIX. Probablemente sexa a linguaxe de script máis popular.

  53. 73

    Atopei que a solución anterior non funcionaba correctamente.
    Necesito cambiar a:

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

  54. 75
  55. 76

    Ola, realmente necesitarei a túa axuda nisto.

    Fixen unha solicitude de obtención ao meu servidor web http://localhost:8000/users/findusers/53.47792/-2.23389/20/
    53.47792 = $latitude
    -2.23389 = $lonxitude
    e 20 = a distancia que quero recuperar

    Non obstante, usando a túa fórmula, recupera todas as filas da miña base de datos

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

    [{“id”:1,”name”:”Frankie Johnnie & Luigo Too”,”address”:”939 W El Camino Real, Mountain View, CA”,”lat”:37.386337280273,”lng”:-122.08582305908, ”distancia”:16079.294719663},{“id”:2,”nome”:”Amici's East Coast Pizzeria”,”enderezo”:”790 Castro St, Mountain View, CA”,”lat”:37.387138366699,”lng”: -122.08323669434,”distance”:16079.175940152},{“id”:3,”name”:”Kapp's Pizza Bar & Grill”,”enderezo”:”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,”distancia”:16077.420540582},{“id”:5,”name”:”Tony & Alba's Pizza & Pasta”,”enderezo”:”619 Escuela Ave, Mountain” View, CA”,”lat”:37.394012451172,”lng”:-122.09552764893,”distance”:16078.563225154},{“id”:6,”name”:”Oregano's Wood-Fired Pizza”,”enderezo”:”4546” El Camino Real, Los Altos, CA”,”lat”:37.401725769043,”lng”:-122.11464691162,”distancia”:16077.937560795},{“ id”:7,”name”:”Os bares e as parrillas”,”enderezo”:”24 Whiteley Street, Manchester”,”lat”:53.485118865967,”lng”:-2.1828699111938,”distancia”:8038.7620112314}]

    Quero recuperar só filas con 20 millas, pero trae todas as filas. Por favor, que estou facendo mal

  56. 77

    Estou buscando unha consulta similar, pero intensifico un pouco: en resumo, trátase de agrupar todas as coordenadas dentro de 2 millas de cada coordenada e despois contar cantas coordenadas en cada grupo e producir só un grupo que teña máis coordenadas, aínda que tes máis dun grupo entre os grupos que teñen o maior número de coordenadas - simplemente saca o grupo aleatorio dos grupos co mesmo número -

¿Que pensas?

Este sitio usa Akismet para reducir o spam. Aprende a procesar os teus datos de comentarios.