PHP y SQL: Calcule o consulte la distancia del gran círculo entre puntos de latitud y longitud con la fórmula de Haversine

Fórmula de Haversine - Calcule la distancia del gran círculo con PHP o MySQL

Este mes he estado programando bastante en PHP y MySQL con respecto a GIS. Espiando en la red, de hecho me costó encontrar algunos de los Cálculos geográficos para encontrar la distancia entre dos ubicaciones, así que quería compartirlas aquí.

Mapa de vuelo de Europa con gran distancia circular

La forma sencilla de calcular una distancia entre dos puntos es utilizar la fórmula de Pitágoras para calcular la hipotenusa de un triángulo (A² + B² = C²). Esto se conoce como distancia euclidiana.

Es un comienzo interesante, pero no se aplica a Geografía, ya que la distancia entre las líneas de latitud y longitud es no a la misma distancia aparte. A medida que te acercas al ecuador, las líneas de latitud se alejan más. Si usa algún tipo de ecuación de triangulación simple, puede medir la distancia con precisión en un lugar y terriblemente mal en el otro, debido a la curvatura de la Tierra.

Gran distancia del círculo

Las rutas que se recorren largas distancias alrededor de la Tierra se conocen como las Gran distancia del círculo. Es decir ... la distancia más corta entre dos puntos en una esfera es diferente a los puntos en un mapa plano. Combine eso con el hecho de que las líneas de latitud y longitud no son equidistantes ... y obtendrá un cálculo difícil.

Aquí hay una fantástica explicación en video de cómo funcionan Great Circles.

La fórmula de Haversine

La distancia usando la curvatura de la Tierra se incorpora en la Fórmula de Haversine, que utiliza trigonometría para permitir la curvatura de la tierra. Cuando encuentras la distancia entre 2 lugares en la tierra (en línea recta), una línea recta es realmente un arco.

Esto es aplicable en los vuelos aéreos. ¿Alguna vez ha mirado el mapa real de vuelos y ha notado que están arqueados? Eso es porque es más corto volar en un arco entre dos puntos que directamente a la ubicación.

PHP: Calcule la distancia entre 2 puntos de latitud y longitud

De todos modos, aquí está la fórmula de PHP para calcular la distancia entre dos puntos (junto con la conversión de millas frente a kilómetros) redondeada a dos lugares decimales.

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 los registros dentro de un rango calculando la distancia en millas usando latitud y longitud

También es posible usar SQL para hacer un cálculo para encontrar todos los registros dentro de una distancia específica. En este ejemplo, voy a consultar MyTable en MySQL para encontrar todos los registros que son menores o iguales a la variable $ distancia (en millas) a mi ubicación en $ latitud y $ longitud:

La consulta para recuperar todos los registros dentro de un distancia calculando la distancia en millas entre dos puntos de latitud y longitud 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."

Necesitarás personalizar esto:

  • $ longitud - esta es una variable de PHP donde paso la longitud del punto.
  • $ latitud - esta es una variable de PHP donde paso la longitud del punto.
  • $ distancia - esta es la distancia a la que le gustaría encontrar todos los registros menores o iguales.
  • mesa - esta es la tabla… querrá reemplazarla con el nombre de su tabla.
  • latitud - este es el campo de su latitud.
  • longitud - este es el campo de tu longitud.

SQL: Recuperar todos los registros dentro de un rango calculando la distancia en kilómetros usando latitud y longitud

Y aquí está la consulta SQL usando kiló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."

Necesitarás personalizar esto:

  • $ longitud - esta es una variable de PHP donde paso la longitud del punto.
  • $ latitud - esta es una variable de PHP donde paso la longitud del punto.
  • $ distancia - esta es la distancia a la que le gustaría encontrar todos los registros menores o iguales.
  • mesa - esta es la tabla… querrá reemplazarla con el nombre de su tabla.
  • latitud - este es el campo de su latitud.
  • longitud - este es el campo de tu longitud.

Utilicé este código en una plataforma de mapeo empresarial que utilizamos para una tienda minorista con más de 1,000 ubicaciones en América del Norte y funcionó a la perfección.

77 Comentarios

  1. 1

    Muchas gracias por compartir. Este fue un trabajo fácil de copiar y pegar y funciona muy bien. Me has ahorrado mucho tiempo.
    FYI para cualquier persona que se traslade a C:
    doble deg2rad (doble deg) {return deg * (3.14159265358979323846 / 180.0); }

  2. 2

    Muy buena publicación, funcionó muy bien, solo tuve que cambiar el nombre de la mesa que sostiene el lat-long. Funciona bastante rápido para ... Tengo una cantidad razonablemente pequeña de lat-long (<400) pero creo que esto se escalaría bien. Buen sitio también - lo acabo de agregar a mi cuenta de del.icio.us y lo revisaré regularmente.

  3. 4
  4. 5
  5. 8

    Creo que su SQL necesita una declaración.
    en lugar de WHERE distancia <= $ distancia que podría necesitar
    usar TENIENDO distancia <= $ distancia

    de lo contrario, gracias por ahorrarme un montón de tiempo y energía.

  6. 10
  7. 11
  8. 12

    Muchas gracias por compartir este código. Me ahorró mucho tiempo de desarrollo. Además, gracias a sus lectores por señalar que una declaración HAVING es necesaria para MySQL 5.x. Muy útil.

  9. 14
  10. 15

    Hola,

    Otra pregunta. ¿Existe una fórmula para las cadenas NMEA como la siguiente?

    1342.7500, N, 10052.2287, E

    $GPRMC,032731.000,A,1342.7500,N,10052.2287,E,0.40,106.01,101106,,*0B

    Gracias,
    Harry

  11. 16

    También descubrí que DONDE no funcionó para mí. Lo cambié a TENER y todo funciona perfecto. Al principio no leí los comentarios y los reescribí usando una selección anidada. Ambos funcionarán bien.

  12. 17
  13. 18

    Increíblemente útil, ¡muchas gracias! Estaba teniendo algunos problemas con el nuevo "TENER", en lugar de "DÓNDE", pero una vez que leí los comentarios aquí (después de aproximadamente media hora de rechinar los dientes con frustración = P), lo hice funcionar bien. Gracias ^ _ ^

  14. 19
  15. 20

    Tenga en cuenta que una declaración selecta como esa será muy intensa computacionalmente y, por lo tanto, lenta. Si tiene muchas de esas consultas, puede empantanar las cosas con bastante rapidez.

    Un enfoque mucho menos intenso es ejecutar una primera selección (cruda) utilizando un área CUADRADA definida por una distancia calculada, es decir, "seleccionar * del nombre de la tabla donde la latitud entre lat1 y lat2 y la longitud entre lon1 y lon2". lat1 = targetlatitude - latdiff, lat2 = targetlatitude + latdiff, similar con lon. latdiff ~ = distancia / 111 (para km), o distancia / 69 para millas, ya que 1 grado de latitud es ~ 111 km (ligera variación ya que la Tierra es ligeramente ovalada, pero suficiente para este propósito). londiff = distancia / (abs (cos (deg2rad (latitud)) * 111)) - o 69 por millas (en realidad, puede tomar un cuadrado un poco más grande para tener en cuenta las variaciones). Luego tome el resultado de eso e introdúzcalo en la selección radial. No olvide tener en cuenta las coordenadas fuera de los límites, es decir, el rango de longitud aceptable es de -180 a +180 y el rango de latitud aceptable es de -90 a +90, en caso de que su latdiff o londiff se encuentre fuera de este rango. . Tenga en cuenta que en la mayoría de los casos esto puede no ser aplicable ya que solo afecta los cálculos sobre una línea a través del océano pacífico de polo a polo, aunque cruza parte de chukotka y parte de alaska.

    Lo que logramos con esto es una reducción significativa en el número de puntos contra los cuales realiza este cálculo. Si tiene un millón de puntos globales en la base de datos distribuidos aproximadamente de manera uniforme y desea buscar dentro de los 100 km, entonces su primera búsqueda (rápida) es de un área de 10000 kilómetros cuadrados y probablemente arrojará unos 20 resultados (basados ​​en una distribución uniforme en superficie de unos 500 millones de kilómetros cuadrados), lo que significa que se ejecuta el cálculo de distancia compleja 20 veces para esta consulta en lugar de un millón de veces.

    • 21
      • 22

        ¡Fantástico consejo! De hecho, trabajé con un desarrollador que escribió una función que tiraba del cuadrado interior y luego una función recursiva que creaba 'cuadrados' alrededor del perímetro para incluir y excluir los puntos restantes. El resultado fue un resultado increíblemente rápido: pudo evaluar millones de puntos en microsegundos.

        Mi enfoque anterior es definitivamente 'crudo' pero capaz. ¡Gracias de nuevo!

        • 23

          Doug

          He estado tratando de usar mysql y php para evaluar si un punto lat long está dentro de un polígono. ¿Sabe si su amigo desarrollador publicó algún ejemplo sobre cómo realizar esta tarea? ¿O conoces algún buen ejemplo? Gracias por adelantado.

  16. 24

    Hola a todos, esta es mi declaración SQL de prueba:

    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

    y Mysql me dice que la distancia, no existe como columna, puedo usar order by, puedo hacerlo sin WHERE, y funciona, pero no con él ...

  17. 26

    Esto es genial, sin embargo, es como vuelan los pájaros. Sería genial intentar incorporar la API de Google Maps a esto de alguna manera (tal vez usando carreteras, etc.) Solo para dar una idea usando una forma diferente de transporte. Todavía tengo que crear una función de recocido simulado en PHP que pueda ofrecer una solución eficiente al problema del vendedor ambulante. Pero creo que puedo reutilizar parte de su código para hacerlo.

  18. 27
  19. 28

    ¡Buen artículo! Encontré muchos artículos que describen cómo calcular la distancia entre dos puntos, pero realmente estaba buscando el fragmento de código SQL.

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

    2 días de investigación para finalmente encontrar esta página que resuelve mi problema. Parece que será mejor que saque mi WolframAlpha y repasaré mis matemáticas. El cambio de DÓNDE a TENER tiene mi script en funcionamiento. GRACIAS

  25. 37
    • 38

      Gracias Georgi. Seguí obteniendo la columna 'distancia' no encontrada. Una vez que cambie el DONDE a TENER funcionó como un encanto!

  26. 39

    Desearía que esta fuera la primera página que encontré sobre esto. Después de probar muchos comandos diferentes, este fue el único que funcionó correctamente y con cambios mínimos necesarios para adaptarse a mi propia base de datos.
    ¡Muchas gracias!

  27. 40

    Desearía que esta fuera la primera página que encontré sobre esto. Después de probar muchos comandos diferentes, este fue el único que funcionó correctamente y con cambios mínimos necesarios para adaptarse a mi propia base de datos.
    ¡Muchas gracias!

  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

    gracias por publicar este útil artículo,  
    pero por alguna razón me gustaría preguntar
    ¿Cómo obtener la distancia entre las coordenadas dentro de mysql db y las coordenadas insertadas en php por el usuario?
    para describir más claramente:
    1.El usuario debe insertar [id] para seleccionar datos específicos de la base de datos y las coordenadas del propio usuario
    2.El archivo php obtiene los datos de destino (coordenadas) usando [id] y luego calcula la distancia entre el usuario y el punto de destino

    ¿O simplemente puede obtener distancia del código a continuación?

    $ qry = “SELECCIONAR *, (((acos (sin ((“. $ latitud. ”* pi () / 180)) * sin ((` Latitud` * pi () / 180)) + cos ((“. $ latitud. ”* pi () / 180)) * cos ((` Latitud` * pi () / 180)) * cos (((“. $ longitud.” - `Longitud`) * pi () / 180) ))) * 180 / pi ()) * 60 * 1.1515 * 1.609344) como distancia DESDE `MyTable` DONDE distancia> =". $ Distancia ". >>>> ¿puedo "quitar" la distancia desde aquí?
    gracias de nuevo,
    Timmy S

  41. 60

    ok, todo lo que he intentado no funciona. Quiero decir, lo que tengo funciona, pero las distancias están muy lejos.

    ¿Alguien podría ver lo que está mal con este código?

    if (isset ($ _ POST ['enviado'])) {$ z = $ _POST ['zipcode']; $ r = $ _POST ['radio']; 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 DONDE 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 ") o morir (mysql_error ()); while ($ fila = mysql_fetch_array ($ sql)) {$ tienda1 = $ fila ['MktName']. "”; $ tienda = $ fila ['LocAddSt']. ””; $ tienda. = $ fila ['LocAddCity']. ”,“. $ fila ['LocAddState']. ” “. $ Fila ['código postal']; $ latitud1 = $ fila ['lat']; $ longitud1 = $ fila ['lon']; $ latitud2 = $ fila ['y1']; $ longitud2 = $ fila ['x1']; $ ciudad = $ fila ['ciudad']; $ estado = $ fila ['estado']; $ dis = getnew ($ latitud1, $ longitud1, $ latitud2, $ longitud2, $ unidad = 'Mi'); // $ dis = distancia ($ lat1, $ lon1, $ lat2, $ lon2); $ verificado = $ fila ['verificado']; if ($ verificado == '1') {echo “”; echo “”. $ tienda. ””; echo $ dis. " kilómetros de distancia"; eco ""; } else {echo “”. $ store. ””; echo $ dis. " kilómetros de distancia"; eco ""; }}}

    mi código functions.php
    function getnew ($ latitud1, $ longitud1, $ latitud2, $ longitud2, $ unidad = 'Mi') {$ theta = $ longitud1 - $ longitud2; $ 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; cambiar ($ unidad) {caso 'Mi': romper; caso 'Km': $ distancia = $ distancia * 1.609344; } return (round ($ distancia, 2)); }

    Gracias de antemano

  42. 61
  43. 62

    Hola Douglas, gran artículo. Encontré su explicación de los conceptos geográficos y el código realmente interesante. Mi única sugerencia sería espaciar y sangrar el código para mostrarlo (como Stackoverflow, por ejemplo). Entiendo que desea ahorrar espacio, pero el espaciado / sangrado de código convencional me facilitaría mucho, como programador, leer y analizar. De todos modos, eso es una pequeña cosa. Mantener el buen trabajo.

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

    parece más rápido (mysql 5.9) usar el doble de la fórmula en la selección y donde:
    $ fórmula = “(((acos (sin ((“. $ latitud. ”* pi () / 180)) * sin ((` Latitud` * pi () / 180)) + cos ((“. $ latitud. ”* Pi () / 180)) * cos ((` Latitud` * pi () / 180)) * cos (((“. $ Longitud.” - `Longitud`) * pi () / 180)))) * 180 / pi ()) * 60 * 1.1515 * 1.609344) ”;
    $ sql = 'SELECCIONAR *,'. $ fórmula. ' como distancia DESDE la tabla DONDE '.. $ fórmula.' <= '. $ distancia;

  51. 71
  52. 72

    Muchas gracias por esquivar este artículo, es muy útil.
    PHP se creó al principio como una plataforma de scripting simple llamada "Página de inicio personal". Hoy en día PHP (la abreviatura de Hypertext Preprocessor) es una alternativa a la tecnología de páginas Active Server (ASP) de Microsoft.

    PHP es un lenguaje del lado del servidor de código abierto que se utiliza para crear páginas web dinámicas. Se puede incrustar en HTML. PHP generalmente se usa junto con una base de datos MySQL en servidores web Linux / UNIX. Probablemente sea el lenguaje de programación más popular.

  53. 73

    Encontré que la solución anterior no funciona correctamente.
    Necesito cambiar a:

    $ qqq = “SELECCIONAR *, (((acos (sin ((“. $ latitud. ”* pi () / 180)) * sin ((` latt` * pi () / 180)) + cos ((”. $ latitud. “* pi () / 180)) * cos ((` latt` * pi () / 180)) * cos (((”. $ longitud.“ - `longt`) * pi () / 180) ))) * 180 / pi ()) * 60 * 1.1515) como distancia DESDE `registro`“;

  54. 75
  55. 76

    Hola, por favor necesito tu ayuda en esto.

    Hice una solicitud de obtención a mi servidor web http://localhost:8000/users/findusers/53.47792/-2.23389/20/
    53.47792 = $ latitud
    -2.23389 = $ longitud
    y 20 = la distancia que quiero recuperar

    Sin embargo, usando su fórmula, recupera todas las filas en mi base de datos

    $ resultados = DB :: select (DB :: raw ("SELECT *, (((acos (sin ((". $ latitud. "* pi () / 180)) * sin ((lat * pi () / 180 )) + cos ((“. $ latitud.” * pi () / 180)) * cos ((lat * pi () / 180)) * cos (((“. $ longitud.” - lng) * pi ( ) / 180)))) * 180 / pi ()) * 60 * 1.1515 * 1.609344) como distancia DESDE los marcadores TENIENDO 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, "nombre": "Pizzería de la costa este de Amici", "dirección": "790 Castro St, Mountain View, CA", "lat": 37.387138366699, "lng": -122.08323669434, "distancia": 16079.175940152}, {"id": 3, "nombre": "Kapp's Pizza Bar & Grill", "dirección": "191 Castro St, Mountain View, CA", "lat": 37.393886566162, "Lng": - 122.07891845703, "distancia": 16078.381373826}, {"id": 4, "nombre": "Pizza de mesa redonda: Mountain View", "dirección": "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 Ver, CA ”,” lat ”: 37.394012451172,” lng ”: - 122.09552764893,” distancia ”: 16078.563225154}, {“ id ”: 6,” nombre ”:” Pizza de leña de orégano ”,” dirección ”:” 4546 El Camino Real, Los Altos, CA ”,” lat ”: 37.401725769043,” lng ”: - 122.11464691162,” distancia ”: 16077.937560795}, {“ id ”: 7,” name ”:” The bars and grills ”,” address ”:” 24 Whiteley Street, Manchester ”,” lat ”: 53.485118865967,” lng ”: - 2.1828699111938,” distance ”: 8038.7620112314}]

    Quiero recuperar solo filas con 20 millas pero trae todas las filas. Por favor que estoy haciendo mal

  56. 77

    Estoy buscando una consulta similar, pero he aumentado un poco; en resumen, esto es para agrupar todas las coordenadas dentro de las 2 millas de cada coordenada y luego contar cuántas coordenadas en cada grupo y generar solo un grupo que tiene la mayor cantidad de coordenadas, incluso si tiene más de un grupo entre los grupos que tienen la mayor cantidad de coordenadas - simplemente genere el grupo aleatorio de los grupos con el mismo número más grande -

¿Qué piensas?

Este sitio usa Akismet para reducir el correo no deseado. Descubra cómo se procesan los datos de sus comentarios.