GeoDjango距离过滤器,使用存储在模型中的距离值-查询

14
我有一个名为 Order 的模型,它有一个 origin 的 PointField 和一个 range 的 IntegerField。此外,还有一个叫做 UserProfile 的模型,它有一个 geo_location 的 PointField。现在我有一个 User 实例,user。我想选择所有距离 Order.originuser.userprofile.geo_location 之间的距离小于 Order.range 模型字段中的值(以米为单位)的 Orders
简化版模型如下:
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.

那么我应该如何实现我想要的功能呢?非常感谢您的任何帮助!

1个回答

11

我认为您需要插入一些SQL才能实现您想要的功能,因为GeoDjango助手没有办法让您创建一个同时是Django F对象(即字段查找)的合适的Distance对象。您没有说明使用的是哪种数据库,但以下是在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 Geometry对象
  • 4326Django的默认SRID,但不是PostGIS的默认值,因此我们必须指定它
  • 我们必须CAST您的字段为double precision,因为您正在使用整数
  • 然后我们必须除以1000,以将米转换为千米,这是我认为我们需要的
  • GeoDjango Point字段有一个访问器.wkt,它给出了它们的WKT表示,我们之前在ST_PointFromText()调用中需要它
注意根据PostGIS文档,ST_Distance()不使用索引,因此您可能希望调查使用ST_DWithin()替代(它在ST_Distance()之后有文档记录)。

感谢您的努力整理这个答案。最终,我采用了这里描述的解决方案:http://blog.adamfast.com/2011/11/radius-limited-searching-with-the-orm/ - Daniel Grezo
谢谢您的帮助回答。但我发现ST_Distance()返回的是经纬度度数而不是米。这是我做的事情,以获得米:providers = providers.extra( where=['ST_Distance(ST_Transform(origin, 2163), ST_Transform(ST_PointFromText(%s, 4326), 2163)) <= range'], params=[user.profile.geo_location.wkt] )SRID 2163是美国圆锥投影。我还发现将其转换为double类型是不必要的。 - Tim Saylor
1
我相信 ST_Distance() 的工作方式取决于您提供的是 Geometry 对象还是 Geography。 (自我的原始答案以来,这可能已经改变或未改变。) - James Aylett
真是救命稻草。 - Jamneck

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接