2009-04-30 8 views
12

Esto puede o no puede ser claro, déjame un comentario si estoy fuera de base, o si necesita más información. Tal vez ya hay una solución para lo que quiero en PHP.Encontrar ciudades dentro de 'X' Kilómetros (o millas)

Estoy buscando una función que agregará o restará una distancia de una longitud O valor de latitud.

Motivo: Tengo una base de datos con todas las latitudes y longitudes y quiero formar una consulta para extraer todas las ciudades dentro de X kilómetros (o millas). Mi consulta sería algo como esto ...

Select * From Cities Where (Longitude > X1 and Longitude < X2) And (Latitude > Y1 and Latitude < Y2) 

Where X1 = Longitude - (distance) 
Where X2 = Longitude + (distance) 

Where Y1 = Latitude - (distance) 
Where Y2 = Latitude + (distance) 

Estoy trabajando en PHP, con una base de datos MySql.

Abierto a cualquier sugerencia también! :)

+0

siempre podría obtener la función usted mismo ... esto parece cálculo de nivel de secundaria, o incluso trigonometría si realmente lo simplifica ... – rmeador

+3

Pensaría que sí, pero la Tierra no es una esfera perfecta, y la variación entre 1 grado de longitud en el ecuador y 1 grado de longitud en otro lugar es sorprendentemente grande. ¡Definitivamente no es tan simple como uno esperaría! –

+0

(vea mi respuesta a continuación :)) –

Respuesta

16

Esta es una consulta de MySQL que va a hacer exactamente lo que quiere. Tenga en cuenta cosas como esta son aproximaciones en general, ya que la Tierra no es perfectamente esférica ni tener esto en cuenta montañas, colinas, valles, etc .. Utilizamos este código en AcademicHomes.com con PHP y MySQL, devuelve registros dentro del radio $ millas de $ latitud, $ longitud.

$res = mysql_query("SELECT 
    * 
FROM 
    your_table 
WHERE 
    (
     (69.1 * (latitude - " . $latitude . ")) * 
     (69.1 * (latitude - " . $latitude . ")) 
    ) + ( 
     (69.1 * (longitude - " . $longitude . ") * COS(" . $latitude . "/57.3)) * 
     (69.1 * (longitude - " . $longitude . ") * COS(" . $latitude . "/57.3)) 
    ) < " . pow($radius, 2) . " 
ORDER BY 
    (
     (69.1 * (latitude - " . $latitude . ")) * 
     (69.1 * (latitude - " . $latitude . ")) 
    ) + ( 
     (69.1 * (longitude - " . $longitude . ") * COS(" . $latitude . "/57.3)) * 
     (69.1 * (longitude - " . $longitude . ") * COS(" . $latitude . "/57.3)) 
    ) ASC"); 
+0

Si no lo hace ¿Te importaría preguntarle a Keith, cuál es tu configuración de índice, en esta consulta? Esto funciona muy bien, pero su exploración de cada fila de la base de datos con claves de Lat/Long – MichaelICE

+3

Hey Mike, Si cuestiones de rendimiento, que establecieron un índice en 'de latitud' y un índice en 'longitud', y ajustar los anteriores consulta mediante el uso de cláusulas BETWEEN para restringir la consulta a un subconjunto más pequeño de registros. Añadir en algo así como: DONDE latitud ENTRE $ latitud - ($ radio/70) y $ latitud + ($ radio/70) y longitud ENTRE $ longitud - ($ radio/70) y $ longitud + ($ radius/70) ... Esto permite que la base de datos use los índices en latitud y longitud. La constante 70 se debe a que la distancia máxima de 1 grado de latitud o longitud se extiende alrededor de 70 millas. –

+1

P.S. En nuestro servidor de desarrollo antiguo con una tabla con 2.9 millones de ciudades/pueblos en todo el mundo, los tiempos de consulta cambian de: Sin índices en latitud/longitud: 10.9 segundos Con índices en latitud/longitud: 0.52 segundos –

0

Hay muchas opciones (malas)

  • calcular la distancia utilizando la fórmula matemática (treat X1-X2 e Y1-Y2) como vectores.

  • Cree una tabla de búsqueda de antemano con todas las combinaciones y mantenga las distancias.

  • Considere el uso de una extensión específica de GIS de MySQL. Aquí está one article que encontré sobre esto.

3

EDITAR: Si tiene, en alguna parte, una lista de todas las ciudades del mundo junto con su lat. y largo. valores, puede hacer una búsqueda. En este caso, ver a mi primer enlace de abajo para la fórmula para calcular el ancho de un grado longitudinal en la latitud alt text:

alt text

Honestamente, las complicaciones detrás de este problema son tales que usted sería mucho mejor usando un servicio como Google Maps para obtener sus datos. Específicamente, la Tierra no es una esfera perfecta, y la distancia entre dos grados varía a medida que se acerca o se aleja del ecuador.

Ver http://en.wikipedia.org/wiki/Geographic_coordinate_system para ejemplos de lo que quiero decir, y echa un vistazo a the Google Maps API.

+0

que es esencialmente la función que proponía que derive. No creo que la leve asférica de la tierra importará a una distancia razonable ... IIRC, la diferencia en la distancia entre los polos y a través del ecuador es como 50 millas, que no es nada en términos del tamaño de la Tierra. – rmeador

+0

Sin embargo, hace una gran diferencia al calcular la posición en la superficie. –

0

La siguiente función es de nerddinner (aplicación de muestra de ASP.NET MVC available on codeplex) (MSSQL).

ALTER FUNCTION [dbo].[DistanceBetween] (@Lat1 as real, 
       @Long1 as real, @Lat2 as real, @Long2 as real) 
RETURNS real 
AS 
BEGIN 

DECLARE @dLat1InRad as float(53); 
SET @dLat1InRad = @Lat1 * (PI()/180.0); 
DECLARE @dLong1InRad as float(53); 
SET @dLong1InRad = @Long1 * (PI()/180.0); 
DECLARE @dLat2InRad as float(53); 
SET @dLat2InRad = @Lat2 * (PI()/180.0); 
DECLARE @dLong2InRad as float(53); 
SET @dLong2InRad = @Long2 * (PI()/180.0); 

DECLARE @dLongitude as float(53); 
SET @dLongitude = @dLong2InRad - @dLong1InRad; 
DECLARE @dLatitude as float(53); 
SET @dLatitude = @dLat2InRad - @dLat1InRad; 
/* Intermediate result a. */ 
DECLARE @a as float(53); 
SET @a = SQUARE (SIN (@dLatitude/2.0)) + COS (@dLat1InRad) 
       * COS (@dLat2InRad) 
       * SQUARE(SIN (@dLongitude/2.0)); 
/* Intermediate result c (great circle distance in Radians). */ 
DECLARE @c as real; 
SET @c = 2.0 * ATN2 (SQRT (@a), SQRT (1.0 - @a)); 
DECLARE @kEarthRadius as real; 
/* SET kEarthRadius = 3956.0 miles */ 
SET @kEarthRadius = 6376.5;  /* kms */ 

DECLARE @dDistance as real; 
SET @dDistance = @kEarthRadius * @c; 
return (@dDistance); 
END

Supongo que esto podría ser útil.

+0

La tierra no tiene un radio constante; esto lo acercará, pero (al menos para mi aplicación anterior) no lo suficientemente cerca. Si no está preocupado por unas pocas (hasta cientos de) millas de diferencia, esta sería una buena forma de hacerlo :) –

1

Según la cantidad de ciudades que incluya, puede calcular previamente la lista. Hacemos esto aquí para una aplicación interna donde una imprecisión de +100m es demasiado para nuestra configuración. Funciona al tener una tabla de dos claves de ubicación1, ubicación2, distancia. Entonces podemos retirar las ubicaciones x distancia de ubicación1 muy rápidamente.

Además, dado que las calcs se pueden hacer fuera de línea, no afecta el funcionamiento del sistema. Los usuarios también obtienen resultados más rápidos.

0

Usted puede utilizar el teorema de Pitágoras para calcular la proximidad de dos pares de puntos lat/lon.

Si tiene dos ubicaciones (alfa y beta) se puede calcular su distancia con:

SQRT(POW(Alpha_lat - Beta_lat,2) + POW(Alpha_lon - Beta_lon,2)) 
+3

Para distancias cortas esto sería bueno, pero nuestro planeta no es plano – TalkingCode

0

Usando la configuración de la siguiente URL, he creado la consulta a continuación. (Tenga en cuenta Im usando codeIgnitor para consultar la base de datos)

http://howto-use-mysql-spatial-ext.blogspot.com/2007/11/using-circular-area-selection.html

function getRadius($point="POINT(-29.8368 30.9096)", $radius=2) 
{ 
    $km = 0.009; 
    $center = "GeomFromText('$point')"; 
    $radius = $radius*$km; 
    $bbox = "CONCAT('POLYGON((', 
     X($center) - $radius, ' ', Y($center) - $radius, ',', 
     X($center) + $radius, ' ', Y($center) - $radius, ',', 
     X($center) + $radius, ' ', Y($center) + $radius, ',', 
     X($center) - $radius, ' ', Y($center) + $radius, ',', 
     X($center) - $radius, ' ', Y($center) - $radius, ' 
    ))')"; 

    $query = $this->db->query(" 
    SELECT id, AsText(latLng) AS latLng, (SQRT(POW(ABS(X(latLng) - X({$center})), 2) + POW(ABS(Y(latLng) - Y({$center})), 2)))/0.009 AS distance 
    FROM crime_listing 
    WHERE Intersects(latLng, GeomFromText($bbox)) 
    AND SQRT(POW(ABS(X(latLng) - X({$center})), 2) + POW(ABS(Y(latLng) - Y({$center})), 2)) < $radius 
    ORDER BY distance 
     "); 

    if($query->num_rows()>0){ 
     return($query->result()); 
    }else{ 
     return false; 
    } 
} 
0

No reinventar la rueda. Esta es una consulta espacial. Use MySQL's built-in spatial extensions para almacenar los datos de coordenadas de latitud y longitud en el native MySQL geometry column type. Luego use la función Distance para buscar puntos que estén dentro de una distancia especificada entre sí.

responsabilidad: esto se basa en la lectura de la documentación, no he intentado esto mismo.

+0

Desde la parte superior de la página que vinculó: Actualmente, MySQL no implementa estas funciones de acuerdo con la especificación. Aquellos que se implementan devuelven el mismo resultado que las funciones correspondientes basadas en MBR. Esto incluye funciones en la siguiente lista que no sean Distance() y Related(). – boatcoder

+1

@ Mark0978 La página contiene esa exención de responsabilidad, pero en mi respuesta recomiendo la función 'Distancia '. La cláusula de exención de responsabilidad, tal como se cita en su comentario, no se aplica a la función 'Distancia'. Por lo tanto, el descargo de responsabilidad no es relevante para mi respuesta. – MarkJ

1

He intentado utilizar el código anterior, y las respuestas estaban fuera por el exceso cuando la distancia entre los puntos estaba en el rango de 20-30 millas, y yo estoy bien con unas pocas millas de error. Hablé con un compañero de mapas mío y en su lugar creamos este. El código es python, pero puedes traducirlo con bastante facilidad. Para evitar la conversión constante a radianes, rehice mi base de datos, convirtiendo los puntos lat/lng de grados a radianes. La parte buena de esto es que la mayor parte de las matemáticas se realiza principalmente una vez.

ra = 3963.1906 # radius @ equator in miles, change to km if you want distance in km 
rb = 3949.90275 # radius @ poles in miles, change to km if you want distance in km 
ra2 = ra * ra 
rb2 = rb * rb 

phi = self.lat 

big_ol_constant = (math.pow(ra2*math.cos(phi), 2) + pow(rb2*math.sin(phi), 2))/ (pow(ra*math.cos(phi), 2) + pow(rb*math.sin(phi), 2)) 

sqlWhere = "%(distance)g > sqrt((power(lat - %(lat)g,2) + power(lng-%(lng)g,2)) * %(big_ol_constant)g)" % { 
    'big_ol_constant': big_ol_constant, 'lat': self.lat, 'lng': self.lng, 'distance': distance} 

# This is the Django portion of it, where the ORM kicks in. sqlWhere is what you would put after the WHERE part of your SQL Query. 
qs = ZipData.objects.extra(where=[sqlWhere]); 

Parece ser muy preciso cuando se distancia de separación es pequeña, y dentro de 10 millas más o menos como la distancia crece a 200 millas, (por supuesto, para entonces, usted tiene problemas con "a vuelo de pájaro" vs " caminos pavimentados ").

Aquí es el modelo ZipData que menciono arriba.

class ZipData(models.Model): 
    zipcode = ZipCodeField(null=False, blank=False, verbose_name="ZipCode", primary_key=True) 
    city = models.CharField(max_length=32, null=False, blank=False) 
    state = models.CharField(max_length=2) 
    lat = models.FloatField(null=False, blank=False) 
    lng = models.FloatField(null=False, blank=False) 

una nota extra, es que se puede recibe gran cantidad de datos geográficos relacionados con los códigos postales en GeoNames.org e incluso tienen algunas API de servicio web que puede utilizar también.

+0

esto es genial Mark0978. ¿Tiene el código completo o django-app que puede compartir como un módulo plug & play? – un33k

+0

Ahora he incluido el objeto modelo. Puede enviarme mi nombre de usuario @ gmail.com y le enviaré los archivos que componen esa aplicación, pero no los consideraría una aplicación reutilizable. – boatcoder

Cuestiones relacionadas