Рекомендации для этого быстро меняются, поэтому я отвечу тем, что считаю наиболее актуальным на 18 января 2020 года.
С GeoDjango
Использование geography=True
с GeoDjango значительно упрощает эту задачу. Это означает, что все хранится в лнгах / латах, но расчеты расстояния производятся в метрах на поверхности сферы. См. документацию
from django.db import models
from django.contrib.gis.db.models import PointField
class Vacancy(models.Model):
location = PointField(srid=4326, geography=True, blank=True, null=True)
Django 3.0
Если у вас есть Django 3.0, вы можете отсортировать всю таблицу, используя следующий запрос. Он использует оператор <->
postgis, что означает, что при сортировке будет использоваться пространственный индекс, а аннотированное расстояние будет точным (для Postgres 9.5+). Обратите внимание, что для «сортировки по расстоянию» неявно требуется расстояние от чего-либо. Первый аргумент Point
- это долгота, а второй - широта (противоположность нормальному соглашению).
from django.contrib.gis.db.models.functions import GeometryDistance
from django.contrib.gis.geos import Point
ref_location = Point(140.0, 40.0, srid=4326)
Vacancy.objects.order_by(GeometryDistance("location", ref_location))
Если вы хотите каким-либо образом использовать расстояние от опорной точки, вам нужно будет аннотировать его:
Vacancy.objects.annotate(distance=GeometryDistance("location", ref_location))\
.order_by("distance")
Если у вас много результатов, вычисление точного расстояния для каждой записи все равно будет медленным. Вам следует уменьшить количество результатов одним из следующих способов:
Ограничьте количество результатов с помощью нарезки набора запросов
Оператор <->
не вычисляет точное расстояние для (большинства) результатов, которые он не возвращает, поэтому нарезка или разбивка результатов на страницы выполняется быстро. Чтобы получить первые 100 результатов:
Vacancy.objects.annotate(distance=GeometryDistance("location", ref_location))\
.order_by("distance")[:100]
Получайте результаты только на определенном расстоянии с dwithin
Если есть максимальное расстояние, на котором вы хотите получить результаты, вы должны использовать dwithin
. Запрос dwithin
django использует ST_DWithin, что означает, что он очень быстрый. Установка geography = True означает, что расчет производится в метрах, а не в градусах. Окончательный запрос для всего в пределах 50 км будет следующим:
Vacancy.objects.filter(location__dwithin=(ref_location, 50000))\
.annotate(distance=GeometryDistance("location", ref_location))\
.order_by("distance")
Это может немного ускорить запросы, даже если вы сокращаете до нескольких результатов.
Второй аргумент dwithin
также принимает django.contrib.gis.measure.D
объектов, которые он преобразует в метры, поэтому вместо 50000
метров вы можете просто использовать D(km=50)
.
Фильтрация на расстоянии
Вы можете фильтровать прямо по аннотированному distance
, но он будет дублировать вызов <->
и будет намного медленнее, чем dwithin
.
Vacancy.objects.annotate(distance=GeometryDistance("location", ref_location))\
.filter(distance__lte=50000)\
.order_by("distance")
Django 2.X
Если у вас нет Django 3.0, вы все равно можете отсортировать всю таблицу, используя Distance
вместо GeometryDistance
, но он использует ST_Distance, что может быть медленным, если это делается для каждой записи и есть много записей. В этом случае вы можете использовать dwithin
, чтобы сузить результаты.
Обратите внимание, что нарезка будет не быстрой, потому что Distance
необходимо вычислить точное расстояние для всего, чтобы отсортировать результаты.
Без GeoDjango
Если у вас нет GeoDjango, вам понадобится формула sql для расчета расстояния. Эффективность и правильность варьируются от ответа к ответу (особенно вокруг полюсов / линии дат), но в целом это будет довольно медленно.
Один из способов ускорить запросы - это проиндексировать lat
и lng
и использовать минимальные / максимальные значения для каждого перед аннотированием расстояния. Математика довольно сложна, потому что ограничивающий «прямоугольник» - это не совсем прямоугольник. См. Здесь: Как рассчитать ограничивающий поле для данного местоположения широты / долготы?
person
Jonathan Richards
schedule
10.09.2018