Django的距离函数未返回Distance对象

3

在解决@Nargiza的问题时,出现了这种意外情况:使用GeoDjango进行3D距离计算

遵循Django文档中的Distance函数:

接受两个地理字段或表达式并返回它们之间的距离,作为Distance对象

而从Distance对象中,我们可以获得每一个支持的单位的距离。

但是:

假设模型为:

class MyModel(models.Model):
    ...
    coordinates = models.PointField()

然后是下面这段内容:
p1 = MyModel.objects.get(id=1).coordinates
p2 = MyModel.objects.get(id=2).coordinates
d = Distance(p1, p2) # This is the function call, 
                     # so d should be a Distance object
print d.m

应该打印出 p1p2 之间的距离,单位为米。

但是,我们得到了以下错误:

AttributeError: 'Distance' object has no attribute 'm'

我们最终找到了一个解决办法(d = Distance(m=p1.distance(p2))),但问题仍然存在:
为什么Distance函数没有返回一个Distance对象?这是一个bug还是我们漏掉了什么?
感谢您的时间。
1个回答

5
这两个并不是非常相关。文档确实说它“返回”距离对象,但还有一个额外的步骤:django.contrib.gis.db.models.functions.Distance 是一个数据库函数,它接受两个表达式(可能包括数据库字段名),并返回一个Func对象,可以作为查询的一部分使用。简单地说,它需要在数据库中执行。它将使用数据库函数(例如postgis ST_Distance)计算距离,然后将其作为django.contrib.gis.measure.Distance对象带回来。因此,除非想要搞砸SQL编译器和数据库连接,否则获取两点之间距离的最简单方法是 Distance(m=p1.distance(p2))编辑:以下是一些代码以说明该点:
你可以在django/contrib/gis/measure.py中查看距离(测量)类的代码。它相当简洁易懂,只是提供了一种方便的方式来进行距离转换、比较和算术运算。
In [1]: from django.contrib.gis.measure import Distance

In [2]: d1 = Distance(mi=10)

In [3]: d2 = Distance(km=15)

In [4]: d1 > d2
Out[4]: True

In [5]: d1 + d2
Out[5]: Distance(mi=19.32056788356001)

In [6]: _.km
Out[6]: 31.09344

现在,让我们来看一下距离函数:
__str__方法添加到模型中,这样当查询集API返回距离值时,我们可以看到它,并缩短db_table名称以便查看查询:
class MyModel(models.Model):
    coordinates = models.PointField()

    class Meta:
        db_table = 'mymodel'

    def __str__(self):
        return f"{self.coordinates} {getattr(self, 'distance', '')}"

创建一些对象并进行简单的select * from查询:
In [7]: from gisexperiments.models import MyModel

In [8]: from django.contrib.gis.geos import Point

In [10]: some_places = MyModel.objects.bulk_create(
    ...:     MyModel(coordinates=Point(i, i, srid=4326)) for i in range(1, 5)
    ...: )

In [11]: MyModel.objects.all()
Out[11]: <QuerySet [<MyModel: SRID=4326;POINT (1 1) >, <MyModel: SRID=4326;POINT (2 2) >, <MyModel: SRID=4326;POINT (3 3) >, <MyModel: SRID=4326;POINT (4 4) >]>

In [12]: str(MyModel.objects.all().query)
Out[12]: 'SELECT "mymodel"."id", "mymodel"."coordinates" FROM "mymodel"'

无聊。让我们使用距离函数将距离值添加到结果中:
In [14]: from django.contrib.gis.db.models.functions import Distance

In [15]: from django.contrib.gis.measure import D  # an alias

In [16]: q = MyModel.objects.annotate(dist=Distance('coordinates', origin))

In [17]: list(q)
Out[17]:
[<MyModel: SRID=4326;POINT (1 1) 157249.597768505 m>,
 <MyModel: SRID=4326;POINT (2 2) 314475.238061007 m>,
 <MyModel: SRID=4326;POINT (3 3) 471652.937856715 m>,
 <MyModel: SRID=4326;POINT (4 4) 628758.663018087 m>]

In [18]: str(q.query)
Out[18]: 'SELECT "mymodel"."id", "mymodel"."coordinates", ST_distance_sphere("mymodel"."coordinates", ST_GeomFromEWKB(\'\\001\\001\\000\\000 \\346\\020\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\\000\'::bytea)) AS "distance" FROM "mymodel"'

你可以看到它使用了ST_distance_sphere sql函数来计算距离,利用了"mymodel"."coordinates"和我们的原点点的字节表示。现在,我们可以在数据库管理系统内部(快速)使用它进行过滤、排序和许多其他操作。
In [19]: q = q.filter(distance__lt=D(km=400).m)

In [20]: list(q)
Out[20]:
[<MyModel: SRID=4326;POINT (1 1) 157249.597768505 m>,
 <MyModel: SRID=4326;POINT (2 2) 314475.238061007 m>]

注意:你需要传递浮点数给过滤器,它无法识别距离对象。


如果需要在答案中添加一些代码,请告诉我。我想这样做,但是很难得到一个最小的东西。稍后我会再试一次。 - Igonato
谢谢您的回复@Igonato,如果您能提供一个代码示例那就太好了!我会保持悬赏以防其他回复 :) - John Moutafis
@JohnMoutafis 添加了一些代码。我对GeoDjango还很陌生,也许有更好的解释方式,但这是我最好的尝试。同时,修复了一个错误。函数Distance返回Func,而不是Lookup对象。 - Igonato

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接