【问题标题】:How to add distance from point as an annotation in GeoDjango如何在 GeoDjango 中添加与点的距离作为注释
【发布时间】:2014-08-02 15:22:14
【问题描述】:

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

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

我也知道像这样按场和距离本身进行过滤:

queryset.filter(point__distance_lte=(to_point, D(mi=radius)))

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

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

【问题讨论】:

    标签: django django-models postgis geodjango


    【解决方案1】:

    这样做对我有用,即我可以在注释上应用过滤器。 为便于阅读而拆分。

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

    希望它可以帮助某人:)

    【讨论】:

      【解决方案2】:

      一种在没有 GeoDjango 的情况下注释和排序的方法。该模型包含一个指向 Coordinates 记录的外键,该记录包含 lat 和 lng 属性。

      def get_nearby_coords(lat, lng, max_distance=10):
              """
              Return objects sorted by distance to specified coordinates
              which distance is less than max_distance given in kilometers
              """
              # Great circle distance formula
              R = 6371
              qs = Precinct.objects.all().annotate(
                  distance=Value(R)*Func(
                          Func(
                              F("coordinates__lat")*Value(math.sin(math.pi/180)),
                              function="sin",
                              output_field=models.FloatField()
                          ) * Value(
                              math.sin(lat*math.pi/180)
                          ) + Func(
                              F("coordinates__lat")* Value(math.pi/180),
                              function="cos",
                              output_field=models.FloatField()
                          ) * Value(
                              math.cos(lat*math.pi/180)
                          ) * Func(
                              Value(lng*math.pi/180) - F("coordinates__lng") * Value(math.pi/180),
                              function="cos",
                              output_field=models.FloatField()
                          ),
                          function="acos"
                      )
              ).order_by("distance")
              if max_distance is not None:
                  qs = qs.filter(distance__lt=max_distance)
              return qs
      

      【讨论】:

        【解决方案3】:

        现代方法之一是设置“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'
                )
            )
        

        【讨论】:

        • 我如何将srid=4326 传递给POINT
        【解决方案4】:

        您可以使用 GeoQuerySet.distance

        cities = City.objects.distance(reference_pnt)
        for city in cities:
            print city.distance()
        

        Link: GeoDjango distance documentaion

        编辑:添加距离属性以及距离过滤器查询

        usr_pnt = fromstr('POINT(-92.69 19.20)', srid=4326)
        City.objects.filter(point__distance_lte=(usr_pnt, D(km=700))).distance(usr_pnt).order_by('distance')
        

        Supported distance lookups

        • distance_lt
        • distance_lte
        • distance_gt
        • distance_gte

        【讨论】:

        • 嘿,感谢您的回答,但我在原始问题中提到了这一点。不幸的是,它不允许在距离属性上使用 .filter() 。或者至少以前似乎没有。如果我的想法不正确,请随时扩展您的答案,详细说明如何以这种方式使用它,如果我可以重现,我会将您的答案更改为已接受的答案。
        • @TomDickin 我更新了几个例子,显示距离查询和 order_by。
        • 那会返回什么?有没有办法在没有 DB 的情况下做这样的事情?
        【解决方案5】:

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

        这仅适用于 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)"
        

        可以这样使用:

        queryset.annotate(distance=Dist('longlat', point="POINT(1.022 -42.029)"))
        

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

        【讨论】:

        • Tats 切割器,但是注释点的格式应该像下面的 point="POINT(1.022 -42.029)" 示例:postgis.net/docs/ST_GeomFromText.html
        • 截至 django 1.11 ImportError: No module named aggregates 抛出。
        • 2020 年有开箱即用的解决方案吗?
        猜你喜欢
        • 1970-01-01
        • 2022-11-15
        • 1970-01-01
        • 2010-12-28
        • 2015-10-24
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2015-04-17
        相关资源
        最近更新 更多