我有以下型号:
class Vacancy(models.Model):
lat = models.FloatField('Latitude', blank=True)
lng = models.FloatField('Longitude', blank=True)
我应该如何进行查询以按距离排序(距离是无穷大)?
如果需要,可以使用PostgreSQL,Django。
首先,最好是制作一个点场,而不是将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')
在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)
的解释
这是一个不需要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)
很多信息已经过时,所以我会回答我认为最新的信息。
使用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)
米。
如果你不想/没有机会使用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
在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)