Categorías
Programación

Calcular distancia entre dos ciudades o puntos con PHP, Ruby…

En los proyectos donde utilizamos Google Maps y en general cualquier proyecto «geolocalizado» una formula imprescindible para conocer y aplicar es la de Haversine, no tengo ni idea cuantos años tendrá, pero sospecho que muchos. El caso es que no tenemos que aprenderla ni entenderla a fondo. Solo necesitamos tener un código fiable de apenas 5 lineas que entendamos «minimamente» para poder aplicar. De hecho era una de mis tareas pendientes desde hace mucho tiempo. Si quieres saltarte las explicaciones la he subido en PHP aquí. Posteriormente añadiré el código en Ruby y investigare alguna forma para hacerlo en MYSQL y cómo podría optimizarla para no realizar estos cálculos bestiales en nuestro querido servidor o hosting. Aquí tienes el ejemplo de búsqueda de posiciones cercanas en un radio de «X» kilómetros.

Imaginate que tienes una aplicación de restaurantes y un usuario «localizado» en Alicante buscando sitios cercanos. Básicamente lo único que tienes que hacer es una consulta a la base de datos sacando sitios de Alicante y realizar el calculo sobre cada uno de ellos y mostrar los 10 mas cercanos. Lógicamente esto tendría una eficiencia que tiraría cualquier servidor si realizas el calculo sobre todos tus registros cada vez que un usuario visite el sitio…

Recientemente he añadido una base de datos SQL con todos los municipios, provincias y comunidades autónomas con su correspondiente latitud y longitud que te podría ser bastante útil. Lo puedes encontrar y descargar aquí, «by the face».

¿Como se calcula? Código en PHP:

Antes de nada, este código es una ligera modificación de este post de Taringa. La formula dada ahí funciona perfectamente, de hecho en el código de github que os dejo hay dos comprobaciones y da en el blanco. Simplemente es una función, no merece convertirla en una clase.. Lets keep it Simple.

/**
 * Formula para sacar distancia entre dos puntos dada la latitud y longitud de dos puntos.
 * Esta distancia tiene que estar dada en notación DECIMAL y no en SEXADECIMAL (Grados, minutos... etc)
 * @param type $latitud 1
 * @param type $longitud 1
 * @param type $latitud 2
 * @param type $longitud 2
 * @return type, Distancia en Kms, con 1 decimal de precisión
 */
function harvestine($lat1, $long1, $lat2, $long2){ 
    //Distancia en kilometros en 1 grado distancia.
    //Distancia en millas nauticas en 1 grado distancia: $mn = 60.098;
    //Distancia en millas en 1 grado distancia: 69.174;
    //Solo aplicable a la tierra, es decir es una constante que cambiaria en la luna, marte... etc.
    $km = 111.302;
    
    //1 Grado = 0.01745329 Radianes    
    $degtorad = 0.01745329;
    
    //1 Radian = 57.29577951 Grados
    $radtodeg = 57.29577951; 
    //La formula que calcula la distancia en grados en una esfera, llamada formula de Harvestine. Para mas informacion hay que mirar en Wikipedia
    //http://es.wikipedia.org/wiki/F%C3%B3rmula_del_Haversine
    $dlong = ($long1 - $long2); 
    $dvalue = (sin($lat1 * $degtorad) * sin($lat2 * $degtorad)) + (cos($lat1 * $degtorad) * cos($lat2 * $degtorad) * cos($dlong * $degtorad)); 
    $dd = acos($dvalue) * $radtodeg; 
    return round(($dd * $km), 2);
}

Los cálculos en trigonometría esférica generalmente necesitan de radianes en vez de grados, si mal no recuerdo. De ahí los constantes para convertir de grado a radian y al revés. Los datos que tienes que proporcionar debes tenerlo en notación DECIMAL. Si quieres obtener salida en millas o millas náuticas solo tienes que cambiar la constante. Es decir, la formula como tal te proporciona la distancia en GRADOS. Cada grado o «degree» en la tierra equivale a 111 kms o unas 69 millas.

Cabe destacar que la distancia obtenida no es exacta al 100% por una simple razón. La tierra no es una esfera perfecta. Digamos es una aproximación muy fiel. Si necesitas mas precisión debes contactar con la NASA.

Como tu y yo sabemos que en nuestra base de datos podemos tener miles o millones de registros con localizaciones, esta formula es inútil sin algún tipo de apaño para optimizar el rendimiento. Es decir, lo suyo seria sacar una cantidad finita de candidatos (10 máximo), realizar el calculo y ordenar el resultado. Aquí tienes un ejemplo de como puedes hacer la consulta con algo de MySQL avanzado. Personalmente me parece ineficiente ya que a simple vista parece que realiza el mismo calculo en TODOS los registros, aunque tampoco lo he investigado demasiado… Aquí tienes la segunda parte del experimento, donde creo que se resuelve en parte el problema.

¿Porque no hacerlo con la API de Google Maps?

Puedes hacer la misma tarea con la API de Google MAPS gratis y sin utilizar el procesador de tu servidor. Es decir lo harías asíncronamente y en el ordenador del cliente. Es decir, seria infinitamente mas rápido y mas eficiente para nosotros. Todo lo que tienes que saber esta aquí. Sin embargo para sitios web esto supone un ligero problema ya que las peticiones son Asíncronas. AJAX y APIs externas en general son un problema para el SEO. Cuando el crawler de Google, Yahoo o Bing pase por tu sitio encontrara a mogollón de código JavaScript. Algo que los robots no saben interpretar muy bien. Como puedes entender sin optimización para los buscadores a tu pagina vas a entrar tu y 3 colegas, algo que no interesa a nadie… De ahí que prefiero hacerlo en mi servidor y sacrificar rendimiento por visitas.

Por Antón Zekeriev Rodin

Soy una persona más o menos normal con sus problemas e inquietudes, pero además de ello un ingeniero y desarrollador de Software con unos 10 años de experiencia en el sector. Actualmente intento montarlo por mi cuenta.

Una respuesta a «Calcular distancia entre dos ciudades o puntos con PHP, Ruby…»

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *