Django sort by distance

前端 未结 7 1030
旧时难觅i
旧时难觅i 2020-11-28 04:34

I have the following model:

class Vacancy(models.Model):
    lat = models.FloatField(\'Latitude\', blank=True)
    lng = models.FloatField(\'Longitude\', bla         


        
7条回答
  •  慢半拍i
    慢半拍i (楼主)
    2020-11-28 05:10

    Here is a solution that does not require GeoDjango.

    from django.db import models
    from django.db.models.expressions import RawSQL
    
    
    class 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 math
    from django.db.backends.signals import connection_created
    from 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)
    

提交回复
热议问题