在Django中使用球面余弦定律通过邻近度过滤邮政编码

ldfqzlk8  于 2023-11-20  发布在  Go
关注(0)|答案(8)|浏览(137)

我正在尝试在Django中处理一个基本商店定位器的邻近搜索。而不是在我的应用程序中使用PostGIS,以便我可以使用GeoDjango的距离过滤器,我想在模型查询中使用Cosines距离公式的球面定律。为了提高效率,我希望所有的计算都在数据库中完成。
一个来自互联网的MySQL查询示例实现了球面余弦定律,如下所示:

  1. SELECT id, (
  2. 3959 * acos( cos( radians(37) ) * cos( radians( lat ) ) *
  3. cos( radians( lng ) - radians(-122) ) + sin( radians(37) ) *
  4. sin( radians( lat ) ) )
  5. )
  6. AS distance FROM stores HAVING distance < 25 ORDER BY distance LIMIT 0 , 20;

字符串
查询需要为每个商店的纬度/纬度值引用Zipcode ForeignKey。我如何在Django模型查询中实现所有这些功能?

yhuiod9q

yhuiod9q1#

有可能执行raw SQL queries in Django
我的建议是,编写查询来拉取一个ID列表(看起来你现在正在做),然后使用ID来拉取关联的模型(在一个常规的、非原始的SQL Django查询中)。尽量保持SQL与方言无关,这样如果你不得不切换数据库,你就不必再担心一件事了。
为了澄清,这里有一个如何做到这一点的例子:

  1. def get_models_within_25 (self):
  2. from django.db import connection, transaction
  3. cursor = connection.cursor()
  4. cursor.execute("""SELECT id, (
  5. 3959 * acos( cos( radians(37) ) * cos( radians( lat ) ) *
  6. cos( radians( lng ) - radians(-122) ) + sin( radians(37) ) *
  7. sin( radians( lat ) ) ) )
  8. AS distance FROM stores HAVING distance < 25
  9. ORDER BY distance LIMIT 0 , 20;""")
  10. ids = [row[0] for row in cursor.fetchall()]
  11. return MyModel.filter(id__in=ids)

字符串
作为免责声明,我不能保证这段代码,因为我已经有几个月没有写任何Django了,但它应该是沿着正确的路线。

展开查看全部
zte4gxcn

zte4gxcn2#

为了跟进Tom的回答,它在SQLite中默认不工作,因为SQLite默认缺少数学函数。没问题,添加它非常简单:

  1. class LocationManager(models.Manager):
  2. def nearby_locations(self, latitude, longitude, radius, max_results=100, use_miles=True):
  3. if use_miles:
  4. distance_unit = 3959
  5. else:
  6. distance_unit = 6371
  7. from django.db import connection, transaction
  8. from mysite import settings
  9. cursor = connection.cursor()
  10. if settings.DATABASE_ENGINE == 'sqlite3':
  11. connection.connection.create_function('acos', 1, math.acos)
  12. connection.connection.create_function('cos', 1, math.cos)
  13. connection.connection.create_function('radians', 1, math.radians)
  14. connection.connection.create_function('sin', 1, math.sin)
  15. sql = """SELECT id, (%f * acos( cos( radians(%f) ) * cos( radians( latitude ) ) *
  16. cos( radians( longitude ) - radians(%f) ) + sin( radians(%f) ) * sin( radians( latitude ) ) ) )
  17. AS distance FROM location_location WHERE distance < %d
  18. ORDER BY distance LIMIT 0 , %d;""" % (distance_unit, latitude, longitude, latitude, int(radius), max_results)
  19. cursor.execute(sql)
  20. ids = [row[0] for row in cursor.fetchall()]
  21. return self.filter(id__in=ids)

字符串

展开查看全部
xoefb8l8

xoefb8l83#

为了跟进Tom,如果你想有一个在postgresql中也能工作的查询,你不能使用AS,因为你会得到一个错误,说'distance'不存在。
你应该把整个球面定律表达式放在WHERE子句中,像这样(它在mysql中也有效):

  1. import math
  2. from django.db import connection, transaction
  3. from django.conf import settings
  4. from django .db import models
  5. class LocationManager(models.Manager):
  6. def nearby_locations(self, latitude, longitude, radius, use_miles=False):
  7. if use_miles:
  8. distance_unit = 3959
  9. else:
  10. distance_unit = 6371
  11. cursor = connection.cursor()
  12. sql = """SELECT id, latitude, longitude FROM locations_location WHERE (%f * acos( cos( radians(%f) ) * cos( radians( latitude ) ) *
  13. cos( radians( longitude ) - radians(%f) ) + sin( radians(%f) ) * sin( radians( latitude ) ) ) ) < %d
  14. """ % (distance_unit, latitude, longitude, latitude, int(radius))
  15. cursor.execute(sql)
  16. ids = [row[0] for row in cursor.fetchall()]
  17. return self.filter(id__in=ids)

字符串
请注意,您必须选择纬度和经度,否则您不能在WHERE子句中使用它。

展开查看全部
qybjjes1

qybjjes14#

为了跟进jboxer的回答,下面是作为自定义管理器的一部分的整个过程,其中一些硬编码的东西变成了变量:

  1. class LocationManager(models.Manager):
  2. def nearby_locations(self, latitude, longitude, radius, max_results=100, use_miles=True):
  3. if use_miles:
  4. distance_unit = 3959
  5. else:
  6. distance_unit = 6371
  7. from django.db import connection, transaction
  8. cursor = connection.cursor()
  9. sql = """SELECT id, (%f * acos( cos( radians(%f) ) * cos( radians( latitude ) ) *
  10. cos( radians( longitude ) - radians(%f) ) + sin( radians(%f) ) * sin( radians( latitude ) ) ) )
  11. AS distance FROM locations_location HAVING distance < %d
  12. ORDER BY distance LIMIT 0 , %d;""" % (distance_unit, latitude, longitude, latitude, int(radius), max_results)
  13. cursor.execute(sql)
  14. ids = [row[0] for row in cursor.fetchall()]
  15. return self.filter(id__in=ids)

字符串

展开查看全部
2exbekwf

2exbekwf5#

JBoxer的回应

  1. def find_cars_within_miles_from_postcode(request, miles, postcode=0):
  2. # create cursor for RAW query
  3. cursor = connection.cursor()
  4. # Get lat and lon from google
  5. lat, lon = getLonLatFromPostcode(postcode)
  6. # Gen query
  7. 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"
  8. # execute the query
  9. cursor.execute(query)
  10. # grab all the IDS form the sql result
  11. ids = [row[0] for row in cursor.fetchall()]
  12. # find cars from ids
  13. cars = Car.objects.filter(id__in=ids)
  14. # return the Cars with these IDS
  15. return HttpResponse( cars )

字符串
这返回了我的汽车从x数量的英里,这工作得很好。然而,原始查询返回多远,他们从某个位置,我认为fieldname是'距离'。
我怎样才能返回这个字段'距离'与我的汽车对象?

展开查看全部
guykilcj

guykilcj6#

使用上面提出的一些答案,我得到了不一致的结果,所以我决定再次使用[这个链接] 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类如下所示

  1. class LocationManager(models.Manager):
  2. def nearby_locations(self, latitude, longitude, radius, max_results=100, use_miles=True):
  3. if use_miles:
  4. distance_unit = 3959
  5. else:
  6. distance_unit = 6371
  7. from django.db import connection, transaction
  8. from mysite import settings
  9. cursor = connection.cursor()
  10. if settings.DATABASE_ENGINE == 'sqlite3':
  11. connection.connection.create_function('acos', 1, math.acos)
  12. connection.connection.create_function('cos', 1, math.cos)
  13. connection.connection.create_function('radians', 1, math.radians)
  14. connection.connection.create_function('sin', 1, math.sin)
  15. sql = """SELECT id, (acos(sin(radians(%f)) * sin(radians(latitude)) + cos(radians(%f))
  16. * cos(radians(latitude)) * cos(radians(%f-longitude))) * %d)
  17. AS distance FROM skills_coveragearea WHERE distance < %f
  18. ORDER BY distance LIMIT 0 , %d;""" % (latitude, latitude, longitude,distance_unit, radius, max_results)
  19. cursor.execute(sql)
  20. ids = [row[0] for row in cursor.fetchall()]
  21. return self.filter(id__in=ids)

字符串
使用网站[链接] http://www.movable-type.co.uk/scripts/latlong.html作为检查,我的结果一致。

展开查看全部
cbeh67ev

cbeh67ev7#

使用Django的数据库函数也可以做到这一点,这意味着你可以使用.annotate()调用添加一个distance_miles列,然后按它排序。下面是一个例子:

  1. from django.db.models import F
  2. from django.db.models.functions import ACos, Cos, Radians, Sin
  3. locations = Location.objects.annotate(
  4. distance_miles = ACos(
  5. Cos(
  6. Radians(input_latitude)
  7. ) * Cos(
  8. Radians(F('latitude'))
  9. ) * Cos(
  10. Radians(F('longitude')) - Radians(input_longitude)
  11. ) + Sin(
  12. Radians(input_latitude)
  13. ) * Sin(Radians(F('latitude')))
  14. ) * 3959
  15. ).order_by('distance_miles')[:10]

字符串

展开查看全部
z0qdvdin

z0qdvdin8#

@classmethod def nearby_locations(cls,latitude,longitude,radius,max_results=1000,use_miles=False):if use_miles:distance_unit = 3959 else:distance_unit = 6371000

  1. from django.db import connection, transaction
  2. from django.conf import settings
  3. cursor = connection.cursor()
  4. sql = """SELECT id, (%f * acos( cos( radians(%f) ) * cos( radians( latitude ) ) *
  5. cos( radians( longitude ) - radians(%f) ) + sin( radians(%f) ) * sin( radians( latitude ) ) ) )
  6. AS distance FROM yourapp_yourmodel
  7. GROUP BY id, latitude, longitude
  8. HAVING (%f * acos( cos( radians(%f) ) * cos( radians( latitude ) ) *
  9. cos( radians( longitude ) - radians(%f) ) + sin( radians(%f) ) * sin( radians( latitude ) ) ) ) < %d
  10. ORDER BY distance OFFSET 0 LIMIT %d;""" % (distance_unit, latitude, longitude, latitude, distance_unit, latitude, longitude, latitude, int(radius), max_results)
  11. cursor.execute(sql)
  12. ids = [row[0] for row in cursor.fetchall()]
  13. return cls.objects.filter(id__in=ids)

字符串

展开查看全部

相关问题