2013-10-31 144 views
8

我有以下型號:Django的按距離排序

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

我應該如何進行查詢按距離排序(距離爲無窮大)?
使用PosgreSQL,GeoDjango(如果需要)。

謝謝。

+0

[閱讀](https://docs.djangoproject.com/en/1.5/ref/models/options/#django.db.models.Options.order_with_respect_to)[docs](https://docs.djangoproject.com/en /1.5/ref/contrib/gis/)! – FallenAngel

+0

是的,我讀過。但沒有明白。將再次閱讀。)謝謝。 – Rukomoynikov

+1

什麼距離。什麼距離? –

回答

23

首先,這是更好地做,而不是把經緯度和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') 

已經有像stackoverlow中的幾個問題,如果你檢查它們會更好。

+0

這是我想明白,爲什麼在這個查詢過濾器,當我不能只是︰'res = mymodel.objects.order_by('location')' – Rukomoynikov

+0

如果你想對位置進行排序,應該有一個參考位置。您可以對特定位置的記錄進行排序。 – cem

+0

而在您的查詢中:D(m =距離),距離是否可變? – Rukomoynikov

18

這是一個不需要GeoDjango使用自定義管理器的解決方案。

class LocationManager(models.Manager): 
    def nearby(self, latitude, longitude, proximity): 
     """ 
     Return all object which distance to specified coordinates 
     is less than proximity given in kilometers 
     """ 
     # Great circle distance formula 
     gcd = """ 
       6371 * acos(
       cos(radians(%s)) * cos(radians(latitude)) 
       * cos(radians(longitude) - radians(%s)) + 
       sin(radians(%s)) * sin(radians(latitude)) 
      ) 
       """ 
     return self.get_queryset()\ 
        .exclude(latitude=None)\ 
        .exclude(longitude=None)\ 
        .annotate(distance=RawSQL(gcd, (latitude, 
                longitude, 
                latitude)))\ 
        .filter(distance__lt=proximity)\ 
        .order_by('distance') 


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

用途如下:

eiffel_tower_5k = Location.objects.nearby(48.8582, 2.2945, 5) 

如果你使用SQLite,你需要的地方添加

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) 
+0

這與PostGIS中的性能對比如何?我猜測它會變慢,但計算將在EC2服務器上完成,而不是RDS,對吧?如果我們只需要一個八角形或甚至正方形的粗略距離,那麼使用自定義管理器會比我假設的PostGIS好得多? – dtgq

+0

計算將在數據庫中完成。 – jdotjdot

+1

真的很喜歡這個解決方案,不需要django GIS,不需要安裝庫,只需要一些數學。我不知道這是否適用於任何分貝?對我來說,它在mysql上完美工作。迄今爲止沒有性能問題,但只查詢大約100條記錄。 – benzkji

19

.distance(ref_location)在Django被刪除> = 1.9,你應該用註解來替代。

from django.contrib.gis.db.models.functions import Distance 
from django.contrib.gis.measure import D 

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=(geom, 0.02)) 
    .filter(location__distance_lte=(ref_location, D(m=2000))) 
    .annotate(distance=Distance('location', ref_location)) 
    .order_by('distance') 

看到this postlocation__dwithin=(geom, 0.02)解釋

+2

注意:'D'實際上是'django.contrib.gis.measure.Distance',它痛苦地與模型函數共享相同的名字 –

0

如果你不想/沒有機會使用gis,這裏是溶劑(django orm sql中的半徑距離函數寫入器):

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 爲sqlite3的,你應當確保有可用功能SIN,COS,弧度,ATAN2,SQRT