此方面的最佳实践变化很快,因此我将回答我认为截至 2020 年 1 月 18 日最新的内容。
使用 GeoDjango
将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)
Django 3.0
如果您有 Django 3.0,则可以使用以下查询对整个表进行排序。它使用 postgis 的 <-> 运算符,这意味着排序将使用空间索引,并且带注释的距离将是准确的(对于 Postgres 9.5+)。请注意,“按距离排序”隐含地需要距离 一些东西。 Point 的第一个参数是经度,第二个是纬度(与正常约定相反)。
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.order_by(GeometryDistance("location", ref_location))
如果您想以任何方式使用与参考点的距离,则需要对其进行注释:
Vacancy.objects.annotate(distance=GeometryDistance("location", ref_location))\
.order_by("distance")
如果您有很多结果,计算每个条目的确切距离仍然会很慢。您应该使用以下方法之一减少结果数量:
使用查询集切片限制结果数
<-> 运算符不会计算它不会返回的(大多数)结果的精确距离,因此对结果进行切片或分页会很快。要获得前 100 个结果:
Vacancy.objects.annotate(distance=GeometryDistance("location", ref_location))\
.order_by("distance")[:100]
用dwithin只能得到一定距离内的结果
如果您想要获得结果的最大距离,您应该使用dwithin。 dwithin django 查询使用 ST_DWithin,这意味着它非常快。 设置 geography=True 表示此计算以米为单位,而不是度数。 对 50 公里内的所有内容的最终查询将是:
Vacancy.objects.filter(location__dwithin=(ref_location, 50000))\
.annotate(distance=GeometryDistance("location", ref_location))\
.order_by("distance")
这可以稍微加快查询速度,即使您将结果分成几个部分。
dwithin 的第二个参数也接受 django.contrib.gis.measure.D 对象,它将其转换为米,因此您可以使用 D(km=50) 代替 50000 米。
过滤距离
您可以直接对带注释的distance 进行过滤,但它会复制<-> 调用,并且比dwithin 慢很多。
Vacancy.objects.annotate(distance=GeometryDistance("location", ref_location))\
.filter(distance__lte=50000)\
.order_by("distance")
Django 2.X
如果您没有 Django 3.0,您仍然可以使用 Distance 而不是 GeometryDistance 对整个表进行排序,但它使用 ST_Distance,如果对每个条目都进行排序可能会很慢并且有很多的条目。如果是这种情况,您可以使用dwithin 来缩小结果范围。
请注意,切片不会很快,因为Distance 需要计算所有事物的确切距离以便对结果进行排序。
没有 GeoDjango
如果您没有 GeoDjango,则需要一个 sql 公式来计算距离。效率和正确性因答案而异(尤其是在极点/日期变更线附近),但总的来说会相当慢。
加快查询速度的一种方法是对lat 和lng 进行索引,并在标注距离之前为每个使用最小值/最大值。数学相当复杂,因为边界“框”并不完全是一个框。见这里:How to calculate the bounding box for a given lat/lng location?