Фильтровать zipcodes по близости в Django со сферическим законом косинусов
Я пытаюсь обработать поиск близости для основного хранилища хранилища в Django. Вместо того, чтобы перетаскивать PostGIS с моим приложением, так что я могу использовать фильтр расстояния GeoDjango, я бы хотел использовать формулу сферического закона формулы Косинуса в модельном запросе. Я бы хотел, чтобы все вычисления выполнялись в базе данных в одном запросе, для эффективности.
Пример запроса MySQL из Интернета, реализующего сферический закон косинусов следующим образом:
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;
Запрос должен ссылаться на Zipcode ForeignKey для каждого значения lat/lng хранилища. Как я могу выполнить всю эту работу в запросе модели Django?
Ответы
Ответ 1
Возможно, выполнить необработанные SQL-запросы в Django.
Мое предложение: напишите запрос, чтобы вытащить список идентификаторов (что похоже на то, что вы сейчас делаете), затем используйте идентификаторы, чтобы вытащить связанные модели (в регулярный, не-сырой SQL-запрос Django), Постарайтесь сохранить свой SQL как диалект-независимый, насколько это возможно, чтобы вам не пришлось беспокоиться о еще одной вещи, если вам когда-либо приходилось переключать базы данных.
Чтобы прояснить, вот пример того, как это сделать:
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)
Как отказ от ответственности, я не могу ручаться за этот код, так как прошло несколько месяцев с тех пор, как я написал любой Django, но он должен быть в правильных строках.
Ответ 2
Чтобы ответить на ответ Tom, он не будет работать в SQLite по умолчанию из-за отсутствия SQLite математических функций по умолчанию. Нет проблем, довольно просто добавить:
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)
Ответ 3
Чтобы следить за Tom, если вы хотите иметь запрос, который также работает в postgresql, вы не можете использовать AS, потому что вы получите сообщение об ошибке "distance" не существует.
Вы должны поместить все сферическое выражение закона в предложение WHERE, как это (он также работает в 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)
Обратите внимание, что вам нужно выбрать широту и долготу, иначе вы не сможете использовать ее в предложении WHERE.
Ответ 4
Просто чтобы ответить на ответ jboxer, вот все это как часть пользовательского менеджера с некоторыми жестко закодированными вещами, превращенными в переменные:
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)
Ответ 5
После ответа 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 )
Это возвращает мои автомобили с х миль, это хорошо работает. Однако необработанный запрос возвращал, насколько далеко они были из определенного места, я думаю, что имя поля было "расстоянием".
Как я могу вернуть это поле "расстояние" с моими автомобильными объектами?
Ответ 6
Используя некоторые из предложенных ответов выше, я получал неустойчивые результаты, поэтому решил снова проверить уравнение
используя [эту ссылку] http://www.movable-type.co.uk/scripts/latlong.html в качестве ссылки, уравнение
d = acos(sin(lat1)*sin(lat2) + cos(lat1)*cos(lat2)*cos(lon2-lon1) ) * 6371
где d
- расстояние, которое нужно вычислить,
lat1,lon1
- координата базовой точки, а lat2,lon2
- координата других точек, которые в нашем случае являются точками в базе данных.
Из приведенных выше ответов класс LocationManager
выглядит следующим образом:
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)
Используя сайт [link] http://www.movable-type.co.uk/scripts/latlong.html в качестве проверки, мои результаты будут согласованы.