web-dev-qa-db-ja.com

Django距離で並べ替え

私は次のモデルを持っています:

class Vacancy(models.Model):
    lat = models.FloatField('Latitude', blank=True)
    lng = models.FloatField('Longitude', blank=True)

距離(距離は無限大)で並べ替えるクエリを作成するにはどうすればよいですか?

必要に応じて、PosgreSQL、GeoDjangoで作業しています。

21
Rukomoynikov

まず、latとlntを分離するのではなく、ポイントフィールドを作成することをお勧めします。

from Django.contrib.gis.db import models

location = models.PointField(null=False, blank=False, srid=4326, verbose_name="Location")

次に、次のようにフィルタリングできます。

from Django.contrib.gis.geos import *
from Django.contrib.gis.measure import D

distance = 2000 
ref_location = Point(1.232433, 1.2323232)

res = yourmodel.objects.filter(location__distance_lte=(ref_location, D(m=distance))).distance(ref_location).order_by('distance')
34
cem

.distance(ref_location)はDjango> = 1.9で削除され、代わりにアノテーションを使用する必要があります。

_from Django.contrib.gis.db.models.functions import Distance
from Django.contrib.gis.measure import D
from Django.contrib.gis.geos import Point

ref_location = Point(1.232433, 1.2323232, srid=4326)
yourmodel.objects.filter(location__distance_lte=(ref_location, D(m=2000)))                                                     
    .annotate(distance=Distance("location", ref_location))                                                                
    .order_by("distance")
_

また、空間インデックスを使用するdwithin演算子を使用して検索を絞り込む必要があります。距離は、クエリを遅くするインデックスを使用しません。

_yourmodel.objects.filter(location__dwithin=(ref_location, 0.02))
    .filter(location__distance_lte=(ref_location, D(m=2000)))
    .annotate(distance=Distance('location', ref_location))
    .order_by('distance')
_

location__dwithin=(ref_location, 0.02)の説明については、 この投稿 を参照してください。

46
cleder

これはGeoDjangoを必要としないソリューションです。

from Django.db import models
from Django.db.models.expressions import RawSQL


class Location(models.Model):
    latitude = models.FloatField()
    longitude = models.FloatField()
    ...


def get_locations_nearby_coords(latitude, longitude, max_distance=None):
    """
    Return objects sorted by distance to specified coordinates
    which distance is less than max_distance given in kilometers
    """
    # Great circle distance formula
    gcd_formula = "6371 * acos(least(greatest(\
    cos(radians(%s)) * cos(radians(latitude)) \
    * cos(radians(longitude) - radians(%s)) + \
    sin(radians(%s)) * sin(radians(latitude)) \
    , -1), 1))"
    distance_raw_sql = RawSQL(
        gcd_formula,
        (latitude, longitude, latitude)
    )
    qs = Location.objects.all() \
    .annotate(distance=distance_raw_sql))\
    .order_by('distance')
    if max_distance is not None:
        qs = qs.filter(distance__lt=max_distance)
    return qs

次のように使用します。

nearby_locations = get_locations_nearby_coords(48.8582, 2.2945, 5)

Sqliteを使用している場合は、どこかに追加する必要があります

import math
from Django.db.backends.signals import connection_created
from Django.dispatch import receiver


@receiver(connection_created)
def extend_sqlite(connection=None, **kwargs):
    if connection.vendor == "sqlite":
        # sqlite doesn't natively support math functions, so add them
        cf = connection.connection.create_function
        cf('acos', 1, math.acos)
        cf('cos', 1, math.cos)
        cf('radians', 1, math.radians)
        cf('sin', 1, math.sin)
        cf('least', 2, max)
        cf('greatest', 2, min)
31
rphlo

多くの情報が古くなっているので、私が思うに最新の情報で答えます。

GeoDjangoで_geography=True_を使用すると、これがはるかに簡単になります。これは、すべてがlng/latで保存されることを意味しますが、距離の計算は球の表面でメートル単位で行われます。 ドキュメントを参照

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

次のクエリを使用してテーブル全体を並べ替えることができますが、ST_Distanceを使用します。これは、すべてのエントリで実行され、エントリが多い場合は遅くなる可能性があります。 「距離による並べ替え」には、暗黙的に距離が必要であることに注意してくださいfrom何か。 Pointの最初の引数は経度で、2番目の引数は緯度です(通常の規則の反対)。

_from Django.contrib.gis.db.models.functions import Distance
from Django.contrib.gis.geos import Point

ref_location = Point(140.0, 40.0, srid=4326)
Vacancy.objects.annotate(distance=Distance("location", ref_location))\
    .order_by("distance")
_

結果が必要な最大距離がある場合は、クエリを最適化できます。 dwithin DjangoクエリはST_DWithinを使用するため、非常に高速です。geography = Trueを設定すると、この計算は度ではなくメートルで行われます。 。これは、ST_Distanceを使用し、速度が遅くなる_distance_lte_を使用する必要がないことを意味します。50km以内のすべての最終クエリは次のようになります。

_from Django.contrib.gis.db.models.functions import Distance
from Django.contrib.gis.geos import Point

ref_location = Point(140.0, 40.0, srid=4326)
Vacancy.objects.filter(location__dwithin=(ref_location, 50000))\
    .annotate(distance=Distance("location", ref_location))\
    .order_by("distance")
_

dwithinの2番目の引数も_Django.contrib.gis.measure.D_オブジェクトを受け入れ、それをメートルに変換するため、_50000_メートルの代わりにD(km=50)を使用できます。

10

Django 3.0には、 GeometryDistance 関数があります。これは、Distanceと同じように機能しますが、 <-> operator 代わりに、ORDER BYクエリで空間インデックスを使用し、dwithinフィルターの必要性を排除します。

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.annotate(
    distance=GeometryDistance('location', ref_location)
).order_by('distance')

Django 3.0がリリースされる前に使用したい場合は、次のようなものを使用できます。

from Django.contrib.gis.db.models.functions import GeoFunc
from Django.db.models import FloatField
from Django.db.models.expressions import Func

class GeometryDistance(GeoFunc):
   output_field = FloatField()
   arity = 2
   function = ''
   arg_joiner = ' <-> '
   geom_param_pos = (0, 1)

   def as_sql(self, *args, **kwargs):
       return Func.as_sql(self, *args, **kwargs)
1
Francisco Couzo

地理情報システムを使用したくない/使用する機会がない場合は、ここに解決策があります(Django orm sql)のhaversine distance fomula writter):

lat = 52.100
lng = 21.021

earth_radius=Value(6371.0, output_field=FloatField())

f1=Func(F('latitude'), function='RADIANS')
latitude2=Value(lat, output_field=FloatField())
f2=Func(latitude2, function='RADIANS')

l1=Func(F('longitude'), function='RADIANS')
longitude2=Value(lng, output_field=FloatField())
l2=Func(longitude2, function='RADIANS')

d_lat=Func(F('latitude'), function='RADIANS') - f2
d_lng=Func(F('longitude'), function='RADIANS') - l2

sin_lat = Func(d_lat/2, function='SIN')
cos_lat1 = Func(f1, function='COS')
cos_lat2 = Func(f2, function='COS')
sin_lng = Func(d_lng/2, function='SIN')

a = Func(sin_lat, 2, function='POW') + cos_lat1 * cos_lat2 * Func(sin_lng, 2, function='POW')
c = 2 * Func(Func(a, function='SQRT'), Func(1 - a, function='SQRT'), function='ATAN2')
d = earth_radius * c

Shop.objects.annotate(d=d).filter(d__lte=10.0)

PSモデルの変更、フィルターのorder_byへの変更、キーワードの変更、パラメーター化

PS2 for sqlite3、使用可能な関数SIN、COS、RADIANS、ATAN2、SQRTがあることを確認する必要があります

1
404pio