Django's Distance function not returning a Distance object

感情迁移 提交于 2019-12-01 08:50:11

Those two aren't that closely related to each other. Docs do indeed say that it "returns" Distance object, but there is an extra step:

django.contrib.gis.db.models.functions.Distance is a database function, it takes two expressions (that may include database field names) and returns a Func object that can be used as a part of a query.

So simply put, it needs to be executed in a database. It will calculate distance using database function (e.g. postgis ST_Distance) and then bring it back as a django.contrib.gis.measure.Distance object.

So, unless one wants to mess with SQL compilers and db connections, easiest way to get Distance between two points is Distance(m=p1.distance(p2))

EDIT: Some code to illustrate the point:

You can check out code for Distance (measurement) class in django/contrib/gis/measure.py. It's fairly small and easy to understand. All it's doing is giving you a convenient way to do conversion, comparison and arithmetic operations with distances:

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

Now, let's take a look at the Distance function:

Add __str__ method to the model so we can see distance value when it returned by the queryset api and short db_table name so we can look in the queries:

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

    class Meta:
        db_table = 'mymodel'

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

Create some objects and do a simple select * from query:

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"'

Boring. Let's use Distance function to add distance value to the result:

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"'

You can see it uses ST_distance_sphere sql function do calculate distance using values from "mymodel"."coordinates" and byte representation of our origin point.

We can now use it for filtering and ordering and lots of other things, all inside the database management system (fast):

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

Notice .m you need to pass float number to the filter, it won't be able to recognize Distance object.

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!