如何使用经纬度筛选落在特定半径内的Django模型

13

我有以下这个模型。

class Location(models.Model):
    name = models.CharField(max_length = 128, blank = True)
    address =models.CharField(max_length = 200, blank= True)
    latitude = models.DecimalField(max_digits=6, decimal_places=3)
    longitude = models.DecimalField(max_digits=6, decimal_places=3)

    def __unicode__(self):
        return self.name

如果我的当前纬度和经度是:

current_lat = 43.648
current_long = 79.404

我做了一些研究,发现了Haversine Equation,它可以计算两个位置坐标之间的距离。下面是我找到的公式:

import math

def distance(origin, destination):
    lat1, lon1 = origin
    lat2, lon2 = destination
    radius = 6371 # km

    dlat = math.radians(lat2-lat1)
    dlon = math.radians(lon2-lon1)
    a = math.sin(dlat/2) * math.sin(dlat/2) + math.cos(math.radians(lat1)) \
        * math.cos(math.radians(lat2)) * math.sin(dlon/2) * math.sin(dlon/2)
    c = 2 * math.atan2(math.sqrt(a), math.sqrt(1-a))
    d = radius * c

    return d

我想要返回所有在10公里半径范围内的位置对象,如何进行筛选以便只返回这个10公里半径范围内的所有位置对象?

LocationsNearMe = Location.objects.filter(#This is where I am stuck)

有没有办法将Haversine方程式应用于过滤中,以便它仅返回在10公里半径范围内的位置对象?

我正在寻找详细的答案。感谢帮助。


1
你应该尝试使用Geodjango https://docs.djangoproject.com/en/dev/ref/contrib/gis/ - user710907
一定要研究一下geodjango,我有一个解决这个问题的方案,但需要使用geodjango:https://gist.github.com/omouse/5623772 - user9903
1
@omouse,感谢您指引我正确的方向。在您的解决方案中,您是正确的,但是在这个模型中,它被分成了两个字段:经度和纬度。您能否在答案部分为这种情况提供您的答案? - deadlock
3个回答

13

您可以使用 filter 进行范围查询。

LocationsNearMe = Location.objects.filter(latitude__gte=(the minimal lat from distance()),
                                          latitude__lte=(the minimal lat from distance()),
                                          (repeat for longitude))

不幸的是,这会以几何平方的形式返回结果(而不是圆形)


12

但是,您可以通过过滤上一步的结果(这些结果应该是较小的子集),并对每个结果检查它们是否在半径范围内,来改进 Brian 提出的方法。

您的用户位于黑点中。Brian 给出的正方形近似返回绿色和橙色点。最坏情况下距离差异可能很大,用户需要比预期多走 sqrt(2) 倍的路程(额外增加了 40% 的距离)。因此,对于所有的橙色和绿色点,值得检查它们从黑点的距离(例如欧几里德距离,如果这些距离确实很短,例如城市导航)是否不大于假定的半径。

图片描述

更新:

如果您想使用 Haversine 距离或(更好的)提到的 GeoDjango,请查看此片段,比较了两个处理附近搜索的 Django 视图:

https://gist.github.com/andilabs/4232b463e5ad2f19c155


谢谢你的回答,我非常感激你对如何解决这个问题的见解! :) - deadlock
主旨已损坏。 - Afshin Mehrabani
1
这是新链接 http://andilabs.github.io/django/devops/tools/postgres/postgis/2015/01/31/postgis-geodjango-english.html - Mahammad Adil Azeem

5
如果您不想使用GeoDjango,那么可以考虑使用Django的数据库函数编写。与原始SQL相比,这还具有能够轻松附加/前置其他ORM过滤器的优点。
from django.db.models.functions import Radians, Power, Sin, Cos, ATan2, Sqrt, Radians
from django.db.models import F

dlat = Radians(F('latitude') - current_lat)
dlong = Radians(F('longitude') - current_long)

a = (Power(Sin(dlat/2), 2) + Cos(Radians(current_lat)) 
    * Cos(Radians(F('latitude'))) * Power(Sin(dlong/2), 2)
)

c = 2 * ATan2(Sqrt(a), Sqrt(1-a))
d = 6371 * c

LocationsNearMe = Location.objects.annotate(distance=d).order_by('distance').filter(distance__lt=10)

所有这些内容在你的models.py文件中应该放在哪里? - cjm
1
创建一个名为LocationQuerySet的类,该类继承自models.QuerySet,并将objects = LocationQuerySet.as_manager()添加到您的Location类中。然后在LocationQuerySet中创建一个自定义函数def locations_near_x_within_y_km(self, current_lat, current_long, y_km),该函数执行上述所有操作并返回self.annotate(distance=d).order_by('distance').filter(distance__lt=y_km)。现在,您可以使用LocationsNearMe = Location.objects.locations_near_x_within_y_within_y_km(50,50,10) - Beolap
这里的10是什么?是英里还是公里? - Mohammad Ashraful Islam
这是参数“y_km”,所以是公里。 - Beolap
最后一行中的 distance 是什么意思? - Hamza Lachi
@HamzaLachi 这是一个带注释的字段。你可以随意命名它。它基本上与你的查询集合一起保留,这样你就可以像我在示例中所做的那样进行过滤或排序。 - Beolap

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接