How to add distance from a point as an annotation in GeoDjango

I have a Geographic model with a single PointField, I want to add an annotation for the distance of each model from a given point, which I can later filter and do additional pickery jiggery.

There is an obvious function queryset.distance(to_point), but this does not actually annotate the request, it just adds a distance attribute for each model in the request, that is, I cannot then apply to it .filter(distance__lte=some_distance).

I also know about filtering by field and removing myself like this:

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

but since I want to make several filters (to get the number of models in different distance ranges), I really do not want the database to calculate the distance from a given point each time, as this could be expensive.

Any ideas? In particular, is there a way to add this as a regular annotation, rather than an inserted attribute of each model?

+4
source share
3 answers

I could not find any baked way to do this, so in the end I created my own aggregation class:

This only works with post_gis, but doing it for another geo db should not be too complicated.

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

This can be used as follows:

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

Does anyone know a better way to do this, please let me know (or tell me why my stupid)

+4
source

You can use GeoQuerySet.distance

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

Link: GeoDjango distance documentation

Edit: add distance attribute along with distance filter queries

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

  • distance_lt
  • distance_lte
  • distance_gt
  • distance_gte
  • dwithin
+2

One modern approach is to set the "output_field" arg to avoid the "Invalid geometry input type:". Withour output_field django tries to convert the result of ST_Distance_Sphere to GEOField and cannot.

    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'
        )
    )
+1
source

Source: https://habr.com/ru/post/1544305/


All Articles