在Django中通过邻近的球面定律过滤拉链码

时间:2009-12-16 19:12:28

标签: python django zipcode proximity

我正在尝试在Django中处理基本商店定位器的邻近搜索。我可以使用GeoDjango的距离滤波器,而不是使用我的应用程序运行PostGIS,我想在模型查询中使用球面定律的距离公式。为了提高效率,我希望在一个查询中在数据库中完成所有计算。

来自互联网的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;

查询需要为每个商店的lat / lng值引用Zipcode ForeignKey。如何在Django模型查询中完成所有这些工作?

7 个答案:

答案 0 :(得分:8)

可能执行raw SQL queries in Django

我的建议是,编写查询以提取ID列表(看起来你现在正在做),然后使用ID来提取相关模型(在常规的非原始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已经有几个月了,但它应该是正确的。

答案 1 :(得分:8)

为了跟进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)

答案 2 :(得分:5)

要跟进Tom,如果你想要一个也适用于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子句中使用它。

答案 3 :(得分: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)

答案 4 :(得分:1)

遵循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英里数返回我的汽车,这很有效。然而,原始查询返回它们离某个位置的距离,我认为字段名称是“距离”。

如何将此字段“距离”与我的汽车对象一起返回?

答案 5 :(得分:0)

使用上面提到的一些答案,我得到了不完整的结果,所以我决定再次检查方程式 使用[this link] 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作为检查,我的结果是一致的。

答案 6 :(得分:0)

也可以使用 Django 的数据库函数来执行此操作,这意味着您可以使用 0 1 ticker line_item A ebitda 30.0 33.0 ebitda_margin 0.3 0.3 other_field 7.0 7.0 sales 100.0 110.0 B ebitda 40.0 44.0 ebitda_margin 0.2 0.2 other_field 8.0 8.0 sales 200.0 220.0 C ebitda 30.0 33.0 ebitda_margin 0.1 0.1 other_field 9.0 9.0 sales 300.0 330.0 调用添加一个 distance_miles 列,然后对其进行排序。举个例子:

.annotate()