códigos postales de filtro por la proximidad de Django con la ley de los cosenos esférica
Pregunta
Estoy tratando de manejar búsqueda de proximidad para un localizador de tienda básico en Django. En lugar de PostGIS recorrido alrededor con mi aplicación sólo por lo que se pueden usar filtro de la distancia a GeoDjango, me gustaría utilizar la ley de los cosenos esférica fórmula de la distancia en una consulta de modelo. Me gustaría que todos los cálculos que se realizan en la base de datos en una consulta, para la eficiencia.
Un ejemplo de MySQL consulta a través de Internet la aplicación de la Ley de los cosenos esférica como esto:
SELECT id, (
3959 * acos( cos( radians(37) ) * cos( radians( lat ) ) *
cos( radians( lng ) - radians(-122) ) + sin( radians(37) ) *
sin( radians( lat ) ) )
)
AS distance FROM stores HAVING distance < 25 ORDER BY distance LIMIT 0 , 20;
La consulta tiene que hacer referencia a la CP ForeignKey para los valores / LNG lat de cada tienda. ¿Cómo puedo hacer todo este trabajo en una consulta de modelo de Django?
Solución
Es posible que la ejecución prima consultas SQL en Django .
Mi sugerencia es, escribir la consulta para extraer una lista de ID (que parece que está haciendo ahora), a continuación, utilizar los identificadores para tirar de los modelos asociados (en un no-crudo-SQL consulta regular, Django) . Trate de mantener su dialecto SQL como independiente como sea posible, de modo que usted no tendrá que preocuparse de una cosa más, si alguna vez tiene que cambiar las bases de datos.
Para aclarar, aquí está un ejemplo de cómo hacerlo:
def get_models_within_25 (self):
from django.db import connection, transaction
cursor = connection.cursor()
cursor.execute("""SELECT id, (
3959 * acos( cos( radians(37) ) * cos( radians( lat ) ) *
cos( radians( lng ) - radians(-122) ) + sin( radians(37) ) *
sin( radians( lat ) ) ) )
AS distance FROM stores HAVING distance < 25
ORDER BY distance LIMIT 0 , 20;""")
ids = [row[0] for row in cursor.fetchall()]
return MyModel.filter(id__in=ids)
Como un descargo de responsabilidad, no puedo responder por este código, ya que han pasado unos meses desde que he dejado de Django, pero debe ser en la dirección correcta.
Otros consejos
Para el seguimiento de la respuesta de Tom, no va a funcionar en SQLite de forma predeterminada debido a la falta de SQLite de funciones matemáticas de forma predeterminada. No hay problema, es bastante sencilla de añadir:
class LocationManager(models.Manager):
def nearby_locations(self, latitude, longitude, radius, max_results=100, use_miles=True):
if use_miles:
distance_unit = 3959
else:
distance_unit = 6371
from django.db import connection, transaction
from mysite import settings
cursor = connection.cursor()
if settings.DATABASE_ENGINE == 'sqlite3':
connection.connection.create_function('acos', 1, math.acos)
connection.connection.create_function('cos', 1, math.cos)
connection.connection.create_function('radians', 1, math.radians)
connection.connection.create_function('sin', 1, math.sin)
sql = """SELECT id, (%f * acos( cos( radians(%f) ) * cos( radians( latitude ) ) *
cos( radians( longitude ) - radians(%f) ) + sin( radians(%f) ) * sin( radians( latitude ) ) ) )
AS distance FROM location_location WHERE distance < %d
ORDER BY distance LIMIT 0 , %d;""" % (distance_unit, latitude, longitude, latitude, int(radius), max_results)
cursor.execute(sql)
ids = [row[0] for row in cursor.fetchall()]
return self.filter(id__in=ids)
Para dar seguimiento a Tom, si usted quiere tener una consulta que también funciona en PostgreSQL, no se puede utilizar AS porque obtendrá un error que dice 'distancia' no existe.
Usted debe poner toda la expresion ley esférica en la cláusula WHERE, como esto (También funciona en MySQL):
import math
from django.db import connection, transaction
from django.conf import settings
from django .db import models
class LocationManager(models.Manager):
def nearby_locations(self, latitude, longitude, radius, use_miles=False):
if use_miles:
distance_unit = 3959
else:
distance_unit = 6371
cursor = connection.cursor()
sql = """SELECT id, latitude, longitude FROM locations_location WHERE (%f * acos( cos( radians(%f) ) * cos( radians( latitude ) ) *
cos( radians( longitude ) - radians(%f) ) + sin( radians(%f) ) * sin( radians( latitude ) ) ) ) < %d
""" % (distance_unit, latitude, longitude, latitude, int(radius))
cursor.execute(sql)
ids = [row[0] for row in cursor.fetchall()]
return self.filter(id__in=ids)
Tenga en cuenta que usted tiene que seleccionar la latitud y longitud, de lo contrario no se puede utilizar en la cláusula WHERE.
Sólo para dar seguimiento a la respuesta de jboxer, aquí está todo el asunto como parte de un administrador personalizado con algunas de las cosas no modificable convirtieron en variables:
class LocationManager(models.Manager):
def nearby_locations(self, latitude, longitude, radius, max_results=100, use_miles=True):
if use_miles:
distance_unit = 3959
else:
distance_unit = 6371
from django.db import connection, transaction
cursor = connection.cursor()
sql = """SELECT id, (%f * acos( cos( radians(%f) ) * cos( radians( latitude ) ) *
cos( radians( longitude ) - radians(%f) ) + sin( radians(%f) ) * sin( radians( latitude ) ) ) )
AS distance FROM locations_location HAVING distance < %d
ORDER BY distance LIMIT 0 , %d;""" % (distance_unit, latitude, longitude, latitude, int(radius), max_results)
cursor.execute(sql)
ids = [row[0] for row in cursor.fetchall()]
return self.filter(id__in=ids)
Tras la respuesta de jboxer
def find_cars_within_miles_from_postcode(request, miles, postcode=0):
# create cursor for RAW query
cursor = connection.cursor()
# Get lat and lon from google
lat, lon = getLonLatFromPostcode(postcode)
# Gen query
query = "SELECT id, ((ACOS(SIN("+lat+" * PI() / 180) * SIN(lat * PI() / 180) + COS("+lat+" * PI() / 180) * COS(lat * PI() / 180) * COS(("+lon+" - lon) * PI() / 180)) * 180 / PI()) * 60 * 1.1515) AS distance FROM app_car HAVING distance<='"+miles+"' ORDER BY distance ASC"
# execute the query
cursor.execute(query)
# grab all the IDS form the sql result
ids = [row[0] for row in cursor.fetchall()]
# find cars from ids
cars = Car.objects.filter(id__in=ids)
# return the Cars with these IDS
return HttpResponse( cars )
Esto devuelve mis coches de x cantidad de millas, esto funciona bien. Sin embargo, la consulta devuelve prima lo lejos que estaban de un cierto lugar, creo que era el nombre del campo 'distancia'.
¿Cómo puedo devolver este campo 'distancia' con mis objetos de coche?
El uso de algunas de las respuestas propuestas anteriormente, conseguía resultados incosistent así que decidí ir a la ecuación de nuevo
utilizando [enlace] http://www.movable-type.co.uk/ scripts / latlong.html como referencia, la ecuación es
d = acos(sin(lat1)*sin(lat2) + cos(lat1)*cos(lat2)*cos(lon2-lon1) ) * 6371
donde d
es la distancia que se calcula,
lat1,lon1
es la coordenada del punto base y lat2,lon2
es la coordenada de los otros puntos que en nuestro caso son puntos en la base de datos.
A partir de las respuestas anteriores, la clase LocationManager
se parece a esto
class LocationManager(models.Manager):
def nearby_locations(self, latitude, longitude, radius, max_results=100, use_miles=True):
if use_miles:
distance_unit = 3959
else:
distance_unit = 6371
from django.db import connection, transaction
from mysite import settings
cursor = connection.cursor()
if settings.DATABASE_ENGINE == 'sqlite3':
connection.connection.create_function('acos', 1, math.acos)
connection.connection.create_function('cos', 1, math.cos)
connection.connection.create_function('radians', 1, math.radians)
connection.connection.create_function('sin', 1, math.sin)
sql = """SELECT id, (acos(sin(radians(%f)) * sin(radians(latitude)) + cos(radians(%f))
* cos(radians(latitude)) * cos(radians(%f-longitude))) * %d)
AS distance FROM skills_coveragearea WHERE distance < %f
ORDER BY distance LIMIT 0 , %d;""" % (latitude, latitude, longitude,distance_unit, radius, max_results)
cursor.execute(sql)
ids = [row[0] for row in cursor.fetchall()]
return self.filter(id__in=ids)
Uso del sitio [link] http: //www.movable-type. co.uk/scripts/latlong.html como cheque, mis resultados cuando sean compatibles.