PHP e SQL: calcular ou consultar a gran distancia do círculo entre os puntos de latitude e lonxitude coa fórmula Haversine

Fórmula Haversine: calcula a gran distancia do círculo con PHP ou 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 é nin unha distancia igual 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 aos puntos nun mapa plano. Combina isto 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

De todos os xeitos, aquí está a fórmula PHP para calcular a distancia entre dous puntos (xunto coa conversión de quilómetros ou quilómetros) redondeados a dúas cifras 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)); 
}

SQL: Recuperar todos os rexistros dentro dun intervalo calculando a distancia en quilómetros usando a latitude e a 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 situació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 súa táboa.
  • latitud - este é o campo da súa latitude.
  • lonxitude - este é o campo da túa lonxitude.

SQL: Recuperar 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 súa 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.

76 Comentarios

  1. 1

    Moitas grazas por compartir. Este foi un traballo fácil de copiar e pegar e funciona moi ben. Aforráchesme moito tempo.
    FYI para todos os que porten a C:
    dobre deg2rad (dobre deg) {return deg * (3.14159265358979323846 / 180.0); }

  2. 2

    Un bo anaco de publicación - funcionou moi ben - só tiven que cambiar o nome da táboa que conté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 moi ben. Un bo sitio tamén. Acabo de engadilo á miña conta del.icio.us e volverei revisalo regularmente.

  3. 4
  4. 5
  5. 8

    Creo que o teu SQL precisa unha declaración de ter.
    no canto de WHERE distancia <= $ distancia que pode ter que facer
    usar TENDO distancia <= $ distancia

    se non, grazas por aforrarme unha chea 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 seus lectores por sinalar que é necesaria unha declaración HAVING para MySQL 5.x. Moi útil.

  9. 14
  10. 15
  11. 16

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

  12. 17
  13. 18

    Increíblemente útil, moitas grazas. Tiven algúns problemas co novo "TENER", en lugar de "ONDE", pero unha vez que lin os comentarios aquí (despois de aproximadamente media hora de queixar os dentes de frustración = P), conseguín que funcionase ben. Grazas ^ _ ^

  14. 19
  15. 20

    Teña presente que unha afirmación tan selecta será moi computacional e, polo tanto, lenta. Se tes moitas desas consultas, pode aturdir as cousas con bastante rapidez.

    Un enfoque moito menos intenso consiste en executar unha primeira selección (bruta) usando unha área SQUARE definida por unha distancia calculada, é dicir, "seleccionar * do nome da mesa onde a latitude entre lat1 e lat2 e a lonxitude entre lon1 e lon2". lat1 = targetlatitude - latdiff, lat2 = targetlatitude + latdiff, similar a lon. latdiff ~ = distancia / 111 (por km), ou distancia / 69 por millas xa que 1 grao de latitude é de ~ 111 km (lixeira variación xa que a terra é lixeiramente ovalada, pero suficiente para este fin). londiff = distancia / (abs (cos (deg2rad (latitude)) * 111)) - ou 69 por millas (realmente pode tomar un cadrado un pouco máis grande para ter en conta as variacións). A continuación, colle o resultado diso e introdúceo na selección radial. Non esquezas ter en conta as coordenadas fóra dos límites, é dicir, o intervalo de lonxitude aceptable é de -180 a +180 e o intervalo de latitude aceptable é de -90 a +90, no caso de que o teu latdiff ou londin fose fóra deste rango. . Nótese que na maioría dos casos isto pode non ser aplicable xa que só afecta os cálculos sobre unha liña a través do océano Pacífico de polo en polo, aínda que se cruza parte de chukotka e parte de Alaska.

    O que conseguimos é unha redución significativa no número de puntos contra os que fai este cálculo. Se ten un millón de puntos globais na base de datos distribuídos aproximadamente de xeito uniforme e desexa buscar dentro de 100 km, entón a súa primeira busca (rápida) ten unha superficie de 10000 km cadrados e probablemente dea uns 20 resultados (baseado nunha distribución uniforme nun superficie de aproximadamente 500 millóns de quilómetros cadrados), o que significa que executa o cálculo da distancia complexa 20 veces para esta consulta en vez de un millón de veces.

    • 21
      • 22

        Fantástico consello! De feito, traballei cun desenvolvedor que escribiu unha función que tiraba do cadrado interior e logo 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 incrible rápido: puido avaliar millóns de puntos en microsegundos.

        A miña visión anterior é definitivamente "bruta" pero capaz. Grazas de novo!

        • 23

          Doug,

          Estiven intentando usar mysql e php para avaliar se un punto lat longo está dentro dun polígono. ¿Sabes se o teu amigo desenvolvedor publicou exemplos 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 estame a dicir que a distancia, non existe como columna, podo usar orde por, podo facelo sen ONDE e funciona, pero non con ela ...

  17. 26

    Isto é xenial, pero é como voan os paxaros. Sería xenial intentar incorporar a API de google maps a isto dalgunha maneira (quizais empregando estradas, etc.) Só para dar unha idea usando unha forma de transporte diferente. Aínda teño que facer unha función de recocido simulada en PHP que sería capaz de ofrecer unha solución eficiente ao problema do vendedor viaxeiro. Pero creo que podo reutilizar parte do teu código para facelo.

  18. 27
  19. 28

    Bo artigo! Atopei moitos artigos que describían como calcular a distancia entre dous puntos, pero realmente buscaba o fragmento SQL.

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

    2 días de investigación para atopar finalmente esta páxina que resolve o meu problema. Parece que é mellor que saco o meu WolframAlpha e me repente 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 fose a primeira páxina que atopei nesta. Despois de probar moitos comandos diferentes, este foi o único que funcionou correctamente e con mínimos cambios necesarios para adaptarse á miña propia base de datos.
    Moitas grazas!

  27. 40

    Gustaríame que fose a primeira páxina que atopei nesta. Despois de probar moitos comandos diferentes, este foi o único que funcionou correctamente e con mínimos cambios 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 algunha razón gustaríame preguntar
    como obter a distancia entre coords dentro de mysql db e coords inseridos 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 os acordos 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 pode obter distancia do código seguinte?

    $ 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 distance> =“. $ Distance. ” >>>> Podo "sacar" a distancia de aquí?
    grazas de novo,
    Timmy S

  41. 60

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

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

    if (isset ($ _ POST ['enviado'])) {$ z = $ _POST ['código postal']; $ r = $ _POST ['radio']; eco "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 WHERE m.zipcode = z1.zipcode AND z2.zipcode = $ z AND (3963 * acos (truncate (sin (z2.lat / 57.2958) * sin (m. zip) 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 morre (mysql_error ()); while ($ row = mysql_fetch_array ($ sql)) {$ store1 = $ row ['MktName']. "”; $ store = $ fila ['LocAddSt']. ””; $ store. = $ row ['LocAddCity']. ",". $ row ['LocAddState']. " “. $ Fila ['código postal']; $ latitude1 = $ fila ['lat']; $ lonxitude1 = $ fila ['lon']; $ latitude2 = $ fila ['y1']; $ lonxitude2 = $ fila ['x1']; $ cidade = $ fila ['cidade']; $ estado = $ fila ['estado']; $ dis = getnew ($ latitude1, $ lonxitude1, $ latitude2, $ lonxitude2, $ unit = 'Mi'); // $ dis = distancia ($ lat1, $ lon1, $ lat2, $ lon2); $ verificado = $ fila ['verificado']; if ($ Verified == '1') {eco ""; eco "". $ store. ""; eco $ dis. "Milla (s) de distancia"; eco ""; } else {eco "". $ store. ""; eco $ dis. "Milla (s) de distancia"; eco ""; }}}

    as miñas funcións.php código
    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 ($ unit) {case 'Mi': break; caso 'Km': $ distancia = $ distancia * 1.609344; } retorno (redondo ($ distancia, 2)); }

    Grazas anticipadamente

  42. 61
  43. 62

    Ei Douglas, estupendo artigo. Pareceume realmente interesante a túa explicación sobre os conceptos xeográficos e o código. A miña única suxestión sería espaciar e sangrar o código para mostrar (como Stackoverflow, por exemplo). Comprendo que quere aforrar espazo, pero o espazamento / sangría convencional do código faríame moito máis doado, como programador, ler e diseccionar. En fin, iso é pouco. Segue así.

  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 DA táboa ONDE '.. $ fórmula.' <= '. $ distancia;

  51. 71
  52. 72

    Moitas grazas por cortar este artigo. É moi útil.
    PHP creouse nun principio como unha simple plataforma de script chamada "Páxina 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 do lado do servidor que se usa para crear páxinas web dinámicas. Pode inserirse 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

    Descubrín 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, por favor, realmente necesito a túa axuda nisto.

    Fixen unha solicitude 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 empregando a fórmula, recupera todas as filas do meu db

    $ results = 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 DE marcadores QUE TEÑEN distancia> = “. $ Distancia));

    [{"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, ”distance”: 16079.175940152}, {“id”: 3, ”name”: ”Kapp's Pizza Bar & Grill”, ”address”: ”191 Castro St, Mountain View, CA”, ”lat”: 37.393886566162, "Lng": - 122.07891845703, "distance": 16078.381373826}, {"id": 4, "name": "Pizza de mesa redonda: 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", "address": "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}]

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

¿Que pensas?

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