具有动态半径的 Django 过滤器位置距离

Django filter location distance with dynamic radius

我在 django 中有 2 个模型,一个区域和一个商店,模型是这样的:

from django.contrib.gis.db import models
from django.contrib.gis.geos import Point
from django.contrib.gis.measure import D
from location_field.models.spatial import LocationField


class Zone(models.Model):
    name = models.CharField(max_length=200)
    location_point = LocationField(based_fields=['city'], zoom=7, default=Point(51.67, 32.65))
    radius = models.IntegerField(default=1000)  # radius in meters

class Shop(models.Model):
    name = models.CharField(max_length=200)
    location_point = LocationField(based_fields=['city'], zoom=7, default=Point(51.67, 32.65), null=True, blank=True)
    zone = models.ForeignKey(Zone, on_delete=models.CASCADE, null=True)

LocationField 是一个 PointField,在 django admin 中有更好的地图。

我希望每个商店根据商店位置、区域位置和半径自动保存 select 区域。如果没有以支持商店为半径的区域,它将是 None。我试过这个查询:

zone_list = Zone.objects.filter(
    location_point__distance_lte=(
        shop.location_point, D(m=models.F('radius'))
    )
)

但是我得到这个错误:

TypeError: float() argument must be a string or a number, not 'F'

我该如何解决这个问题?

这似乎发生在 django.contrib.gis.measureMeasureBase class 内部(Distance/D inherits from) and more specifically in the default_units method 它试图将 str 或数字输入值转换为 float 但收到 F 表达式。

我们可以做的解决方法是 annotate Distance小心这个 Distance 方法,因为它来自shop.location_point 和当前 location_point 之间的 GeoDjango 地理数据库函数 ) 然后我们可以过滤 <= 比实例 radius:

from django.contrib.gis.db.models.functions import Distance

zone_list = Zone.objects.annotate(
    distance=Distance('location_point', shop.location_point)
).filter(distance__lte=F('radius'))

感谢@e4c5 的出色回答:GeoDjango filter by distance from a model field

另一种方法是完全消除 annotation 部分并直接通过 Distance:

进行过滤
from django.contrib.gis.db.models.functions import Distance

zone_list = Zone.objects.filter(
    radius_gte=Distance('location_point', shop.location_point)
)


我把这个留在这里是为了评论的连续性:

您可以尝试将 F('radius') 结果转换为 FloatField(),使用 Cast() 方法将整数转换为浮点数。

zone_list = Zone.objects.filter(
    location_point__distance_lte=(
        shop.location_point, 
        D(m=Cast('radius', output_field=models.FloatField()))
    )
)