Search code examples
djangodistancegeodjango

Annotating Django records with GeoDjango Distance objects fails


I have a table with a GeoDjango GeometryField, and records include both points and polygons. I need to return those records with Point geometry within a given distance in km from a given point, for an external API. I first filter for Point records, annotate those with a Distance object, then filter for distance.

I find the generation of the filtered queryset fed to the serializer works fine in my IDE. But when I run the view, it hangs on the annotate() step, with this error:

File "/MyApp/api/views.py", line 102, in get
    qs1 = qs0.annotate(distance=Distance('geom', pnt))
TypeError: __init__() takes from 1 to 2 positional arguments but 3 were given

What I can't figure out is why I can step through all the lines to create qs2, as the qs.count() and qs2 sorted results show, but when I run that code via a URL, it breaks at annotate().

model

from django.contrib.gis.db import models as geomodels

class PlaceGeom(models.Model):
    place = models.ForeignKey(Place,related_name='geoms')
    geom = geomodels.GeometryField(null=True, blank=True, srid=4326)

view

from django.contrib.gis.geos import Polygon, Point
from django.contrib.gis.measure import D, Distance

class SpatialAPIView(generics.ListAPIView):
    def get(self, format=None, *args, **kwargs):
        params = self.request.query_params
    lon = params.get('lon', None)
    lat = params.get('lat', None)
    dist = params.get('km', None)
        
        pnt = Point(float(lon), float(lat), srid=4326)

        print(dist)
        >> 3

        qs0 = PlaceGeom.objects.extra(where=["geometrytype(geom) LIKE 'POINT'"])
        qs0.count()
        >> 1887148

        qs1 = qs0.annotate(distance=Distance('geom', pnt))
        qs2 = qs1.filter(geom__distance_lte=(pnt, D(km=dist))).order_by('distance')

        # results are sorted
        print(["{:.4f}".format(p.distance.km) for p in qs2][:5])
        >> ['0.0635', '0.1071', '0.1283', '0.4274', '1.0647']

        filtered = qs2[:pagesize] if pagesize and pagesize < 200 else qs2[:20]
        
        serial = PlaceSerializer
        serializer = serial(filtered, many=True, context={
            'request': self.request})
        serialized_data = serializer.data
        result = {
            "count": qs.count(),
            "type": "FeatureCollection",
            "features": serialized_data
        }
        return JsonResponse(result, safe=False, json_dumps_params={'ensure_ascii': False, 'indent': 2})

Solution

  • This question was answered correctly by @iain-shelvington in his comment. There are two Distance functions in different parts of the django.contrib.gs package. The # of arguments is different for each, as the error indicated. django.contrib.gis.db.models.function.Distance was appropriate for annotate() here.