Django sort by distance Django sort by distance python python

Django sort by distance


the .distance(ref_location) is removed in django >=1.9 you should use an annotation instead.

from django.contrib.gis.db.models.functions import Distancefrom django.contrib.gis.measure import Dfrom django.contrib.gis.geos import Pointref_location = Point(1.232433, 1.2323232, srid=4326)yourmodel.objects.filter(location__distance_lte=(ref_location, D(m=2000)))                                                         .annotate(distance=Distance("location", ref_location))                                                                    .order_by("distance")

also you should narrow down your search with the dwithin operator which uses the spatial index, distance does not use the index which slows your query down:

yourmodel.objects.filter(location__dwithin=(ref_location, 0.02))    .filter(location__distance_lte=(ref_location, D(m=2000)))    .annotate(distance=Distance('location', ref_location))    .order_by('distance')

see this post for an explanation of location__dwithin=(ref_location, 0.02)


Here is a solution that does not require GeoDjango.

from django.db import modelsfrom django.db.models.expressions import RawSQLclass Location(models.Model):    latitude = models.FloatField()    longitude = models.FloatField()    ...def get_locations_nearby_coords(latitude, longitude, max_distance=None):    """    Return objects sorted by distance to specified coordinates    which distance is less than max_distance given in kilometers    """    # Great circle distance formula    gcd_formula = "6371 * acos(least(greatest(\    cos(radians(%s)) * cos(radians(latitude)) \    * cos(radians(longitude) - radians(%s)) + \    sin(radians(%s)) * sin(radians(latitude)) \    , -1), 1))"    distance_raw_sql = RawSQL(        gcd_formula,        (latitude, longitude, latitude)    )    qs = Location.objects.all() \    .annotate(distance=distance_raw_sql))\    .order_by('distance')    if max_distance is not None:        qs = qs.filter(distance__lt=max_distance)    return qs

Use as follow:

nearby_locations = get_locations_nearby_coords(48.8582, 2.2945, 5)

If you are using sqlite you need to add somewhere

import mathfrom django.db.backends.signals import connection_createdfrom django.dispatch import receiver@receiver(connection_created)def extend_sqlite(connection=None, **kwargs):    if connection.vendor == "sqlite":        # sqlite doesn't natively support math functions, so add them        cf = connection.connection.create_function        cf('acos', 1, math.acos)        cf('cos', 1, math.cos)        cf('radians', 1, math.radians)        cf('sin', 1, math.sin)        cf('least', 2, min)        cf('greatest', 2, max)


Note: Please check cleder's answer below which mentions about deprecation issue (distance -> annotation) in Django versions.

First of all, it is better to make a point field instead of making lat and lnt separated:

from django.contrib.gis.db import modelslocation = models.PointField(null=False, blank=False, srid=4326, verbose_name='Location')

Then, you can filter it like that:

from django.contrib.gis.geos import Pointfrom django.contrib.gis.measure import Ddistance = 2000 ref_location = Point(1.232433, 1.2323232)res = YourModel.objects.filter(    location__distance_lte=(        ref_location,        D(m=distance)    )).distance(    ref_location).order_by(    'distance')