I have an Order
model, that has an origin
PointField and a range
IntegerField. Furthermore, there is an UserProfile
model, which has a geo_location
PointField. Now, I have an User
instance, user
. I want to select all Orders
, whose distance between Order.origin
and user.userprofile.geo_location
is less then the value (meters) in the Order.range
model field.
So again, simplified models:
class Order(models.Model):
origin = models.PointField()
range = models.IntegerField(blank=True, default=10000)
class UserProfile(models.Model):
geo_location = models.PointField()
I've got this working (passing the distance statically):
>>> Order.objects.filter(origin__distance_lte=(user.profile.geo_location, D(m=3000)))
My next (unsuccessful) try was to use an F() expression to use the value from the Order.range
field:
>>> Order.objects.filter(origin__distance_lte=(user.profile.geo_location, D(m=F('range'))))
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/Users/danger/devel/.virtualenvs/proj/lib/python2.7/site-packages/django/contrib/gis/measure.py", line 165, in __init__
self.m, self._default_unit = self.default_units(kwargs)
File "/Users/danger/devel/.virtualenvs/proj/lib/python2.7/site-packages/django/contrib/gis/measure.py", line 49, in default_units
if not isinstance(value, float): value = float(value)
TypeError: float() argument must be a string or a number
I think the problem is that the D() isn't ran lazily - I can understand that.
So I tried to just take the raw value from the range
field (integer) which I supposed to work, but:
>>> Order.objects.filter(origin__distance_lte=(user.profile.geo_location, F('range')))
Traceback (most recent call last):
File "<console>", line 1, in <module>
File "/Users/danger/devel/.virtualenvs/proj/lib/python2.7/site-packages/django/db/models/query.py", line 69, in __repr__
data = list(self[:REPR_OUTPUT_SIZE + 1])
File "/Users/danger/devel/.virtualenvs/proj/lib/python2.7/site-packages/django/db/models/query.py", line 84, in __len__
self._result_cache.extend(self._iter)
File "/Users/danger/devel/.virtualenvs/proj/lib/python2.7/site-packages/django/db/models/query.py", line 273, in iterator
for row in compiler.results_iter():
File "/Users/danger/devel/.virtualenvs/proj/lib/python2.7/site-packages/django/db/models/sql/compiler.py", line 680, in results_iter
for rows in self.execute_sql(MULTI):
File "/Users/danger/devel/.virtualenvs/proj/lib/python2.7/site-packages/django/db/models/sql/compiler.py", line 725, in execute_sql
sql, params = self.as_sql()
File "/Users/danger/devel/.virtualenvs/proj/lib/python2.7/site-packages/django/db/models/sql/compiler.py", line 68, in as_sql
where, w_params = self.query.where.as_sql(qn=qn, connection=self.connection)
File "/Users/danger/devel/.virtualenvs/proj/lib/python2.7/site-packages/django/db/models/sql/where.py", line 92, in as_sql
sql, params = child.as_sql(qn=qn, connection=connection)
File "/Users/danger/devel/.virtualenvs/proj/lib/python2.7/site-packages/django/db/models/sql/where.py", line 95, in as_sql
sql, params = self.make_atom(child, qn, connection)
File "/Users/danger/devel/.virtualenvs/proj/lib/python2.7/site-packages/django/contrib/gis/db/models/sql/where.py", line 47, in make_atom
spatial_sql = connection.ops.spatial_lookup_sql(data, lookup_type, params_or_value, lvalue.field, qn)
File "/Users/danger/devel/.virtualenvs/proj/lib/python2.7/site-packages/django/contrib/gis/db/backends/postgis/operations.py", line 531, in spatial_lookup_sql
raise ValueError('Argument type should be %s, got %s instead.' % (arg_type, type(value[1])))
ValueError: Argument type should be (<class 'decimal.Decimal'>, <class 'django.contrib.gis.measure.Distance'>, <type 'float'>, <type 'int'>, <type 'long'>), got <class 'django.db.models.expressions.F'> instead.
So how can I accomplish what I am trying to? Any help is appreciated!
I think you're going to have to drop some SQL in to do what you want, as the GeoDjango helpers don't have a way of you making a suitable Distance
object that is simultaneously a Django F
object (ie field lookup). You don't say which database you're using, but here's how you do it with PostGIS:
Order.objects.all().extra(
where=['ST_Distance(origin, ST_PointFromText(%s, 4326)) <= CAST(range AS double precision) / 1000'],
params=[user.profile.geo_location.wkt]
)
Let's explain what's happening here, because it's pretty thorny. Going from the left:
.extra()
allows you to add extra fields and restrictions into a query; here we're adding a restrictionST_Distance()
is the PostGIS function that the GeoDjango distance_lte
operator converts into (from the Django documentation)ST_PointFromText()
converts from WKT syntax into a PostGIS Geometry object4326
is the default SRID for Django, but not for PostGIS so we have to specify itCAST
your field to double precision because you're using an integerPoint
fields have an accessor .wkt
which gives their WKT representation, which we needed earlier in our ST_PointFromText()
callNote that according to the PostGIS documentation, ST_Distance()
doesn't use indexes, so you may want to investigate using ST_DWithin()
instead (it's documented right after ST_Distance()
).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With