如何在GeoDjango中添加点距离作为注释

Tom*_*eld 8 django postgis django-models geodjango

我有一个带有单个 PointField 的地理模型,我希望为每个模型与给定点的距离添加注释,稍后我可以对其进行过滤并进行额外的 jiggery pokery。

有一个明显的queryset.distance(to_point)功能,但这实际上并没有注释查询集,它只是向查询集中的每个模型添加一个距离属性,这意味着我.filter(distance__lte=some_distance)以后不能应用它。

我也知道按场和距离本身进行过滤,如下所示:

queryset.filter(point__distance_lte=(to_point, D(mi=radius)))
Run Code Online (Sandbox Code Playgroud)

但由于我想做多个过滤器(以获得不同距离范围内的模型计数),我真的不想让 DB 每次都计算与给定点的距离,因为这可能很昂贵。

有任何想法吗?具体来说,有没有办法将其添加为常规注释而不是每个模型的插入属性?

Tom*_*eld 6

我找不到任何烘焙方法,所以最后我创建了自己的聚合类:

这仅适用于 post_gis,但为另一个地理数据库制作一个不应该太棘手。

from django.db.models import Aggregate, FloatField
from django.db.models.sql.aggregates import Aggregate as SQLAggregate


class Dist(Aggregate):
    def add_to_query(self, query, alias, col, source, is_summary):
        source = FloatField()
        aggregate = SQLDist(
            col, source=source, is_summary=is_summary, **self.extra)
        query.aggregates[alias] = aggregate


class SQLDist(SQLAggregate):
    sql_function = 'ST_Distance_Sphere'
    sql_template = "%(function)s(ST_GeomFromText('%(point)s'), %(field)s)"
Run Code Online (Sandbox Code Playgroud)

这可以按如下方式使用:

queryset.annotate(distance=Dist('longlat', point="POINT(1.022 -42.029)"))
Run Code Online (Sandbox Code Playgroud)

任何人都知道这样做的更好方法,请告诉我(或告诉我为什么我的很愚蠢)

  • 2020 年有开箱即用的解决方案吗? (2认同)

Pow*_*tar 6

这样做对我有用,即我可以在注释上应用过滤器。为了可读性而分解。

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


intMiles  = 200
destPoint = Point(5, 23)

queryset0 = Address.objects.all().order_by('-postcode')
        
queryset1 = queryset0.annotate(distance=Distance('myPointField' , destPoint ))
queryset2 = queryset1.filter(distance__lte=D(mi=intMiles))
Run Code Online (Sandbox Code Playgroud)

希望它对某人有帮助:)


Eug*_*sov 5

现代方法之一是设置“output_field”参数以避免«不正确的几何输入类型:»。我们的 output_field django 试图将 ST_Distance_Sphere 浮点结果转换为 GEOField 并且不能。

    queryset = self.objects.annotate(
        distance=Func(
            Func(
                F('addresses__location'),
                Func(
                    Value('POINT(1.022 -42.029)'),
                    function='ST_GeomFromText'
                ),
                function='ST_Distance_Sphere',
                output_field=models.FloatField()
            ),
            function='round'
        )
    )
Run Code Online (Sandbox Code Playgroud)