Django按距离排序

问题描述 投票:18回答:6

我有以下型号:

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

我应该如何进行查询以按距离排序(距离是无穷大)?

如果需要,可以使用PostgreSQL,Django。

python django geodjango
6个回答
34
投票

首先,最好是制作一个点场,而不是将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')

44
投票

在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')

请参阅this post以获得location__dwithin=(ref_location, 0.02)的解释


31
投票

这是一个不需要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(least(greatest(\
    cos(radians(%s)) * cos(radians(latitude)) \
    * cos(radians(longitude) - radians(%s)) + \
    sin(radians(%s)) * sin(radians(latitude)) \
    , -1), 1))"
    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)
        cf('least', 2, max)
        cf('greatest', 2, min)

7
投票

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

使用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表示此计算以米为单位,而不是度数。这意味着你永远不需要使用使用ST_Distance并且速度慢的distance_lte。对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)米。


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 for sqlite3,你应该确保,有可用的功能SIN,COS,RADIANS,ATAN2,SQRT


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)
© www.soinside.com 2019 - 2024. All rights reserved.