[GIS] GeoDjango with PostGIS: distance_lte filter is inaccurate

geodjangopostgis

My view code (relevant part):

location = self.GOOGLE_MAPS.geocode(lookup)
if len(location):
    page = int(request.POST.get('page')) or 0
    start = page * settings.PHOTOS_PER_BATCH
    end = (page + 1) * settings.PHOTOS_PER_BATCH
    geo_range = int(request.POST.get('range',  self.DEFAULT_RANGE))
    geo = location[0].get('geometry', {}).get('location')
    pnt_str = 'SRID=4326;POINT({} {})'.format(geo['lng'], geo['lat'])
    pnt = GEOSGeometry(pnt_str)

    if geo_range > 0:
        # short version
        # photos = Photo.objects.filter(photographer__locations__point__distance_lte=(pnt, D(km=geo_range)))

        #debugable version
        locations = Location.objects.filter(point__distance_lte=(pnt, D(km=geo_range)))\
            .distance(pnt).order_by('distance')
        photographers = Photographer.objects.filter(locations__in=locations)
        photos = Photo.objects.filter(photographer__in=photographers).order_by('?')
    else:
        photos = Photo.objects.all()

    #render the view
    context = {
        'formatted_address': location[0].get('formatted_address'),
        'photos': photos[start:end],
        'geo': geo,
        'total': len(photos)
    }

    return render(request, self.TEMPLATE_NAME, context)

Distance display is made by a custom django filter:

from django import template
from proto.models import Location

register = template.Library()


def distance(origin, destination):
    return int(destination.distance(origin) * Location.SCALE)

register.filter('distance', distance)

Finally, the template code:

{{ point|distance:p.locations.all.0.point }} KM

The problem is that even when the range is set to 1000 M (10 KM), results that seem to have the distance of 14 of 16 KM are still included.
With a range of 20 KM, result of 25 and 28 (and so on) are also included (although they are excluded from the previous result set).

Is it and SRID Issue?

Also, I don't quite understand why should I multiply by 1000 to get the filtering done, and then divide by only 100 to display the distance.

Best Answer

If you are using Django 1.9, you can use the Distance geographic database function to make annotations to your queryset. The disance then becomes an attribute of your queryset, so you can potentially avoid using a custom template tag alltogether.

from django.contrib.gis.db.models.functions import Distance
photos = photos.annotate(
    distance=Distance('photographer__locations__point', pnt)
)

I am not sure if this is exactly what you are looking for. However, if this works for you, the distance attribute is a Distance measure object. So in your template you can access your favourite measure directly without a custom tag (km in the example):

{{ photo.distance.km }}

This might also resolve your problems with the filtering, at least it would make sure the distance is displayed correct in your template.

Related Question