# in your models.py or managers.py
class PlaceManager(models.Manager):
def nearest_to(self, point, number=5, geom_column='geom', from_srid=4326, to_srid=4326):
"""finds the model objects nearest to a given point
`point` is a tuple of (x, y) in the spatial ref sys given by `from_srid`
returns a list of tuples, sorted by increasing distance from the given `point`.
each tuple being (model_object, dist), where distance is in the units of the
spatial ref sys given by `to_srid`"""
if not isinstance(point, tuple):
raise TypeError
from string import Template
cursor = connection.cursor()
x, y = point
table = self.model._meta.db_table
sql = Template("""
SELECT id,
Length(Transform(SetSRID(MakeLine($geom_column, GeomFromText('POINT(' || $x || ' ' || $y || ')', $from_srid)), $from_srid), $to_srid))
FROM $table
WHERE $geom_column IS NOT NULL
ORDER BY Distance(GeomFromText('POINT($x $y)', $from_srid), $geom_column) ASC
LIMIT $number;
""")
cursor.execute(sql.substitute(locals()))
nearbys = cursor.fetchall()
# get a list of primary keys of the nearby model objects
ids = [p[0] for p in nearbys]
# get a list of distances from the model objects
dists = [p[1] for p in nearbys]
places = self.filter(id__in=ids)
# the QuerySet comes back in an undefined order; let's
# order it by distance from the given point
def order_by(objects, listing, name):
"""a convenience method that takes a list of objects,
and orders them by comparing an attribute given by `name`
to a sorted listing of values of the same length."""
sorted = []
for i in listing:
for obj in objects:
if getattr(obj, name) == i:
sorted.append(obj)
return sorted
return zip(order_by(places, ids, 'id'), dists)
# ex. REPL
>>> Places.objects.nearest_to((-87.96, 42.06), number=5, to_srid=26971)
[(<Place: 600 S See Gwun Ave, Mount Prospect, IL 60056, USA>, 1908.34541121444), (<Place: 48 Raupp Blvd, Buffalo Grove, IL 60089, USA>, 9209.1395558741897), (<Place: 1000 S Milwaukee Ave, IL 60090, USA>, 7996.2420822250497), (<Place: 1175 Howard Ave, Des Plaines, IL 60018, USA>, 8095.9994070327703), (<Place: 701 Thorndale Ave, Wood Dale, IL 60191, USA>, 9613.4684521103609)]
Comments