Фильтрация почтовых индексов по близости в Джанго со сферическим законом косинусов - PullRequest
10 голосов
/ 16 декабря 2009

Я пытаюсь выполнить поиск близости для базового магазина в Джанго. Вместо того, чтобы использовать 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?

Ответы [ 6 ]

8 голосов
/ 14 июня 2010

Чтобы продолжить ответ Тома, он не будет работать в 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)
8 голосов
/ 16 декабря 2009

Возможно выполнение необработанных SQL-запросов в Django .

Мое предложение заключается в том, чтобы написать запрос, чтобы получить список идентификаторов (что, похоже, вы делаете сейчас), а затем использовать идентификаторы для извлечения связанных моделей (в обычном запросе Django, не являющемся необработанным SQL) , Постарайтесь, чтобы ваш 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, но он должен быть в правильном направлении.

5 голосов
/ 24 декабря 2010

Чтобы проконсультироваться с Томом, если вы хотите иметь запрос, который также работает в postgresql, вы не можете использовать AS, потому что получите сообщение о том, что «расстояние» не существует.

Вы должны поместить выражение целого сферического закона в предложение 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 голосов
/ 13 февраля 2010

Просто чтобы проконтролировать ответ 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)
1 голос
/ 22 февраля 2010

После ответа 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 )

Это возвращает мои машины с x миль, это работает хорошо. Однако необработанный запрос возвратил, как далеко они были от определенного местоположения, я думаю, что именем поля было «расстояние».

Как я могу вернуть это поле 'расстояние' с моими автомобильными объектами?

0 голосов
/ 25 сентября 2014

Используя некоторые из предложенных ответов выше, я получал незаметные результаты, поэтому решил снова проверить уравнение используя [эту ссылку] 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)

Использование сайта [ссылка] http://www.movable -type.co.uk / scripts / latlong.html в качестве проверки, мои результаты, если они согласуются

...