Django按距离排序

时间:2013-10-31 10:27:44

标签: python django geodjango

我有以下型号:

class Vacancy(models.Model):
    lat = models.FloatField('Latitude', blank=True)
    lng = models.FloatField('Longitude', blank=True)

如何根据距离(距离是无穷大)进行查询?

如果需要,可以使用PosgreSQL,GeoDjango。

9 个答案:

答案 0 :(得分:43)

在django> = 1.9中删除了.distance(ref_location),您应该使用注释。

from django.contrib.gis.db.models.functions import Distance
from django.contrib.gis.measure import D
from django.contrib.gis.geos import Point

ref_location = Point(1.232433, 1.2323232, srid=4326)
yourmodel.objects.filter(location__distance_lte=(ref_location, D(m=2000)))                                                     
    .annotate(distance=Distance("location", ref_location))                                                                
    .order_by("distance")

您也应该使用使用空间索引的dwithin运算符缩小搜索范围,距离不会使用会降低查询速度的索引:

yourmodel.objects.filter(location__dwithin=(ref_location, 0.02))
    .filter(location__distance_lte=(ref_location, D(m=2000)))
    .annotate(distance=Distance('location', ref_location))
    .order_by('distance')

有关location__dwithin=(ref_location, 0.02)

的说明,请参阅this post

答案 1 :(得分:32)

首先,最好是制作一个点场,而不是将lat和lnt分开:

from django.contrib.gis.db import models

location = models.PointField(null=False, blank=False, srid=4326, verbose_name="Location")

然后,您可以像这样过滤它:

from django.contrib.gis.geos import *
from django.contrib.gis.measure import D

distance = 2000 
ref_location = Point(1.232433, 1.2323232)

res = yourmodel.objects.filter(location__distance_lte=(ref_location, D(m=distance))).distance(ref_location).order_by('distance')

答案 2 :(得分:30)

这是一个不需要GeoDjango的解决方案。

from django.db import models
from django.db.models.expressions import RawSQL


class Location(models.Model):
    latitude = models.FloatField()
    longitude = models.FloatField()
    ...


def get_locations_nearby_coords(latitude, longitude, max_distance=None):
    """
    Return objects sorted by distance to specified coordinates
    which distance is less than max_distance given in kilometers
    """
    # Great circle distance formula
    gcd_formula = "6371 * acos(cos(radians(%s)) * \
    cos(radians(latitude)) \
    * cos(radians(longitude) - radians(%s)) + \
    sin(radians(%s)) * sin(radians(latitude)))"
    distance_raw_sql = RawSQL(
        gcd_formula,
        (latitude, longitude, latitude)
    )
    qs = Location.objects.all() \
    .annotate(distance=distance_raw_sql))\
    .order_by('distance')
    if max_distance is not None:
        qs = qs.filter(distance__lt=max_distance)
    return qs

使用如下:

nearby_locations = get_locations_nearby_coords(48.8582, 2.2945, 5)

如果您使用的是sqlite,则需要添加某个地方

import math
from django.db.backends.signals import connection_created
from django.dispatch import receiver


@receiver(connection_created)
def extend_sqlite(connection=None, **kwargs):
    if connection.vendor == "sqlite":
        # sqlite doesn't natively support math functions, so add them
        cf = connection.connection.create_function
        cf('acos', 1, math.acos)
        cf('cos', 1, math.cos)
        cf('radians', 1, math.radians)
        cf('sin', 1, math.sin)

答案 3 :(得分:6)

很多信息已经过时,所以我将以我认为是最新的信息来回答。

geography=True与GeoDjango结合使用会更加容易。这意味着一切都以lng / lat的形式存储,但是距离的计算是以球体表面上的米为单位的。 See the docs

from django.db import models
from django.contrib.gis.db.models import PointField

class Vacancy(models.Model):
    location = PointField(srid=4326, geography=True, blank=True, null=True)

您可以使用以下查询对整个表进行排序,但是它使用ST_Distance,如果在每个条目上都完成并且有很多条目,这可能会很慢。请注意,“按距离排序”隐式要求距某物 的距离。 Point的第一个参数是经度,第二个参数是纬度(与常规惯例相反)。

from django.contrib.gis.db.models.functions import Distance
from django.contrib.gis.geos import Point

ref_location = Point(140.0, 40.0, srid=4326)
Vacancy.objects.annotate(distance=Distance("location", ref_location))\
    .order_by("distance")

如果要获得最大距离,可以优化查询。 dwithin django查询使用ST_DWithin,这意味着它非常快。 设置geography = True表示此计算以米而不是度为单位。这意味着您永远不需要使用distance_lte,它使用ST_Distance且速度很慢。对于50公里以内的所有事物的最终查询为:

from django.contrib.gis.db.models.functions import Distance
from django.contrib.gis.geos import Point

ref_location = Point(140.0, 40.0, srid=4326)
Vacancy.objects.filter(location__dwithin=(ref_location, 50000))\
    .annotate(distance=Distance("location", ref_location))\
    .order_by("distance")

dwithin的第二个参数也接受django.contrib.gis.measure.D个对象,它将转换为米,因此您可以只使用50000来代替D(km=50)米。

答案 4 :(得分:1)

如果你不想/没有机会使用gis,这里是sollution(django orm sql中的hasrsine distance fomula writter):

lat = 52.100
lng = 21.021

earth_radius=Value(6371.0, output_field=FloatField())

f1=Func(F('latitude'), function='RADIANS')
latitude2=Value(lat, output_field=FloatField())
f2=Func(latitude2, function='RADIANS')

l1=Func(F('longitude'), function='RADIANS')
longitude2=Value(lng, output_field=FloatField())
l2=Func(longitude2, function='RADIANS')

d_lat=Func(F('latitude'), function='RADIANS') - f2
d_lng=Func(F('longitude'), function='RADIANS') - l2

sin_lat = Func(d_lat/2, function='SIN')
cos_lat1 = Func(f1, function='COS')
cos_lat2 = Func(f2, function='COS')
sin_lng = Func(d_lng/2, function='SIN')

a = Func(sin_lat, 2, function='POW') + cos_lat1 * cos_lat2 * Func(sin_lng, 2, function='POW')
c = 2 * Func(Func(a, function='SQRT'), Func(1 - a, function='SQRT'), function='ATAN2')
d = earth_radius * c

Shop.objects.annotate(d=d).filter(d__lte=10.0)

PS 更改模型,将过滤器更改为order_by,更改关键字和参数化

PS2 对于sqlite3,你应该确保有可用的功能SIN,COS,RADIANS,ATAN2,SQRT

答案 5 :(得分:1)

在Django 3.0上,将有一个GeometryDistance函数,其功能与Distance相同,但是使用的是<-> operator,该函数在ORDER BY查询中使用了空间索引,无需使用dwithin过滤器:

from django.contrib.gis.db.models.functions import GeometryDistance
from django.contrib.gis.geos import Point

ref_location = Point(140.0, 40.0, srid=4326)
Vacancy.objects.annotate(
    distance=GeometryDistance('location', ref_location)
).order_by('distance')

如果要在Django 3.0发布之前使用它,则可以使用类似以下的内容:

from django.contrib.gis.db.models.functions import GeoFunc
from django.db.models import FloatField
from django.db.models.expressions import Func

class GeometryDistance(GeoFunc):
   output_field = FloatField()
   arity = 2
   function = ''
   arg_joiner = ' <-> '
   geom_param_pos = (0, 1)

   def as_sql(self, *args, **kwargs):
       return Func.as_sql(self, *args, **kwargs)

答案 6 :(得分:0)

如果您不想使用GeoDjango,可以使用以下代码:

YourModel.objects.annotate(distance=Sqrt(
                Power(Abs(model_field_lat - ref_location_lat), Value(2)) +
                Power(Abs(model_field_long - ref_location_long), Value(2)))) \
                .order_by('distance')

用模型的纬度和经度替换model_field_lat和model_field_long。另外,将ref_location_lat和ref_location_long替换为要计算与之的距离的纬度和经度。

答案 7 :(得分:0)

如果您不想更改模型,即,将lat和lng保留为单独的字段,甚至不想使用太多的Geodjango并希望通过一些基本代码解决此问题,那么这里就是解决方案; < / p>

origin = (some_latitude, some_longitude) #coordinates from where you want to measure distance
distance = {} #creating a dict which will store the distance of users.I am using usernames as keys and the distance as values.
for m in models.objects.all():
    dest = (m.latitude, m.longitude)
    distance[m.username] = round(geodesic(origin, dest).kilometers, 2) #here i am using geodesic function which takes two arguments, origin(coordinates from where the distance is to be calculated) and dest(to which distance is to be calculated) and round function rounds off the float to two decimal places

#Here i sort the distance dict as per value.So minimum distant users will be first.
s_d = sorted(distance.items(), key=lambda x: x[1]) #note that sorted function returns a list of tuples as a result not a dict.Those tuples have keys as their first elements and vaues as 2nd.

new_model_list = []
for i in range(len(s_d)):
    new_model_list.append(models.objects.get(username=s_d[i][0]))

现在,new_model_list将包含按距离排序的所有用户。通过对其进行迭代,您将根据距离对他们进行排序。

答案 8 :(得分:0)

在views.py中,将CustomHaystackGEOSpatialFilter用于filter_backends:

class LocationGeoSearchViewSet(HaystackViewSet):
    index_models = [yourModel]
    serializer_class = LocationSerializer
    filter_backends = [CustomHaystackGEOSpatialFilter]

在filters.py中定义CustomHaystackGEOSpatialFilter并覆盖apply_filters方法,以便您可以按以下方式排序距离并限制结果计数:

class CustomHaystackGEOSpatialFilter(HaystackGEOSpatialFilter):
    # point_field = 'location'
   def apply_filters(self, queryset, applicable_filters=None, applicable_exclusions=None):
        if applicable_filters:
            queryset = queryset.dwithin(**applicable_filters["dwithin"]).distance(
                **applicable_filters["distance"]).order_by("distance")[:100]
        return queryset