Фильтр расстояний GeoDjango со значением расстояния, сохраненным в модели - запрос - PullRequest
14 голосов
/ 03 марта 2012

У меня есть модель Order, которая имеет origin PointField и range IntegerField.Кроме того, есть модель UserProfile, которая имеет geo_location PointField.Теперь у меня есть экземпляр User, user.Я хочу выбрать все Orders, расстояние между которыми Order.origin и user.userprofile.geo_location меньше значения (в метрах) в поле модели Order.range.

Итак, еще раз, упрощенные модели:

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 Ответ

8 голосов
/ 11 марта 2012

Я думаю, что вам придется добавить немного SQL, чтобы сделать то, что вы хотите, поскольку помощники 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 преобразует (из документация Django )
  • ST_PointFromText() преобразуется из синтаксиса WKT в объект геометрии PostGIS
  • 4326 равен SRID по умолчанию для Django , но не для PostGIS, поэтому мы имеемчтобы указать его
  • нам нужно CAST вашего поля с двойной точностью, потому что вы используете целое число
  • , тогда мы должны разделить на 1000, чтобы преобразовать из метровв километры, которые я считаю - это то, что нам нужно
  • Поля GeoDjango Point имеют аксессор .wkt, который дает их представление WKT, которое нам было нужно ранее при нашем ST_PointFromText() вызове

Обратите внимание, что согласно документации PostGIS, ST_Distance() не использует индексы, поэтому вы можете захотеть исследовать, используя вместо этого ST_DWithin() (это задокументировано сразу после ST_Distance()).

...