Skip to content

Instantly share code, notes, and snippets.

@rchrd2
Last active February 14, 2022 08:15
Show Gist options
  • Save rchrd2/5e0b014640a459a14ef038975d2a3683 to your computer and use it in GitHub Desktop.
Save rchrd2/5e0b014640a459a14ef038975d2a3683 to your computer and use it in GitHub Desktop.
Django object manager with Haversine distance annotation method (aka filter by distance with mysql)
from django.db import models
from with_distance_manager import WithDistanceManager
class Foo(models.Model):
longitude = models.DecimalField(max_digits=19, decimal_places=10, null=True)
latitude = models.DecimalField(max_digits=19, decimal_places=10, null=True)
objects = WithDistanceManager()
class WithDistanceManager(models.Manager):
def with_distance(self, latitude, longitude):
"""
Returns a QuerySet of locations annotated with their distance from the
given point. This can then be filtered.
Usage:
Foo.objects.within(lat, lon).filter(distance__lt=10).count()
@see http://stackoverflow.com/a/31715920/1373318
"""
class Sin(Func):
function = 'SIN'
class Cos(Func):
function = 'COS'
class Acos(Func):
function = 'ACOS'
class Radians(Func):
function = 'RADIANS'
radlat = Radians(latitude) # given latitude
radlong = Radians(longitude) # given longitude
radflat = Radians(F('latitude'))
radflong = Radians(F('longitude'))
# Note 3959.0 is for miles. Use 6371 for kilometers
Expression = 3959.0 * Acos(Cos(radlat) * Cos(radflat) *
Cos(radflong - radlong) +
Sin(radlat) * Sin(radflat))
return self.get_queryset()\
.exclude(latitude=None)\
.exclude(longitude=None)\
.annotate(distance=Expression)
@rchrd2
Copy link
Author

rchrd2 commented Jan 2, 2017

Note this is a packaging of http://stackoverflow.com/a/31715920/1373318

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment