2012-03-03 182 views
14

我有一个Order模型,它有一个origin PointField和一个range IntegerField。此外,还有一个UserProfile模型,其中有一个geo_location PointField。现在,我有一个User实例,user。我想选择所有Orders,其中Order.originuser.userprofile.geo_location之间的距离小于Order.range模型字段中的值(米)。GeoDjango距离过滤器存储在模型中的距离值

如此反复,简化模型:

class Order(models.Model): 
    origin = models.PointField() 
    range = models.IntegerField(blank=True, default=10000) 

class UserProfile(models.Model): 
    geo_location = models.PointField() 

我已经得到了这个工作(传递距离静态):

>>> Order.objects.filter(origin__distance_lte=(user.profile.geo_location, D(m=3000))) 

我的下一个(不成功)尝试是使用一个F()表达式使用Order.range字段的值:

>>> 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 

我认为问题在于D()不是懒惰地跑 - 我可以理解。 于是,我就只取原始值从range场(整数),我应该工作,但是:

>>> 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. 

那么,怎样才能做到我什么,我想?任何帮助表示赞赏!

回答

8

我认为你将不得不放弃一些SQ​​L来做你想做的事情,因为GeoDjango助手没有办法让你制作合适的Distance对象,该对象同时也是Django F对象(即字段抬头)。你不说你正在使用的数据库,但这里是你如何在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] 
) 

让我们来解释一下这里发生了什么,因为它是相当棘手。从左侧开始:

  • .extra()允许您在查询中添加额外的字段和限制;在这里我们增加一个限制
  • ST_Distance()是PostGIS的功能,该GeoDjango内置distance_lte操作转换成(从the Django documentation
  • ST_PointFromText()从WKT语法转换为PostGIS几何对象
  • 4326the default SRID for Django,但不适合的PostGIS所以我们必须指定它
  • 我们必须CAST您的字段以双精度,因为您使用的是整数
  • 那么我们必须除以1000以便将米转换成公里,其中我相信是我们需要
  • GeoDjango内置Point领域有一个访问.wkt这给他们的WKT表示,这是我们在ST_PointFromText()请致电需要

注意,根据PostGIS的文档,ST_Distance()不使用索引,因此您可能需要使用ST_DWithin()进行调查(它记录在ST_Distance()之后)。

+0

谢谢你的努力把这个答案放在一起。最后,我最终得到了这里描述的解决方案:http://blog.adamfast.com/2011/11/radius-limited-searching-with-the-orm/ – 2012-03-21 16:14:27

+0

感谢您的有用答案。但是,我发现ST_Distance()返回的是lon/lat度而不是米。以下是我所做的计量表: providers = providers.extra( 其中= ['ST_Distance(ST_Transform(origin,2163),ST_Transform(ST_PointFromText(%s,4326),2163))<= range'], params = [user.profile.geo_location.wkt] ) SRID 2163是美国的圆锥投影。我发现铸造也是不必要的。 – 2015-07-07 22:15:09

+1

我相信'ST_Distance()'的工作方式会有所不同,具体取决于您是否给它“几何”对象或“地理”。 (自从我的原始答复以来,这可能会也可能没有改变。) – 2015-09-07 11:24:22