Django 的 Distance 函数不返回 Distance 对象

Joh*_*fis 3 python django geodjango

这是在使用 @Nargiza:使用 GeoDjango 进行 3d 距离计算解决此问题的过程中出现的意外行为。

遵循关于距离 函数的Django 文档:

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

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

但:

设模型为:

class MyModel(models.Model):
    ...
    coordinates = models.PointField()
Run Code Online (Sandbox Code Playgroud)

然后是以下内容:

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
Run Code Online (Sandbox Code Playgroud)

p1应打印和之间的距离p2(以米为单位)。

相反,我们收到以下错误:

AttributeError: 'Distance' object has no attribute 'm'
Run Code Online (Sandbox Code Playgroud)

我们最终找到了解决方法(d = Distance(m=p1.distance(p2))),但问题仍然存在:

为什么Distance 函数没有返回Distance 对象
这是一个错误,还是我们错过了什么?

在此先感谢您的时间。

Igo*_*ato 6

这两者之间的关系并不密切。文档确实说它“返回” Distance 对象,但有一个额外的步骤:

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
Run Code Online (Sandbox Code Playgroud)

现在,让我们看一下距离函数:

将方法添加__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', '')}"
Run Code Online (Sandbox Code Playgroud)

创建一些对象并执行简单的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"'
Run Code Online (Sandbox Code Playgroud)

无聊的。让我们使用 Distance 函数将距离值添加到结果中:

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"'
Run Code Online (Sandbox Code Playgroud)

您可以看到它使用sql 函数使用我们点的ST_distance_sphere值和字节表示来计算距离。"mymodel"."coordinates"origin

我们现在可以使用它进行过滤和排序以及许多其他事情,所有这些都在数据库管理系统内(快速):

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>]
Run Code Online (Sandbox Code Playgroud)

请注意,.m您需要将浮点数传递给过滤器,它将无法识别距离对象。