【问题标题】:Django: sorting objects by distanceDjango:按距离排序对象
【发布时间】:2019-01-29 12:38:51
【问题描述】:

我有这个模型

class Company(models.Model):
name = models.CharField(max_length = 50)
description = models.TextField()
latitude = models.FloatField()
longitude = models.FloatField()
owner = models.ForeignKey(User, on_delete = models.CASCADE, related_name = "company_owner")
category = models.ForeignKey(Category, on_delete = models.CASCADE)
def __str__(self):
    return self.name
class Meta:
    verbose_name_plural = "Companies"

def get_absolute_url(self):
    return reverse('category_list')
    #if want to redirect to its detail page then
    # return reverse('company_detail' ,kwargs = {'pk' : self.pk})

def get_distance(self):
    ip = get('https://api.ipify.org').text
    reader = geoip2.database.Reader('categories/GeoLite2-City.mmdb')
    response = reader.city(ip)
    current_lat = response.location.latitude
    current_lon = response.location.longitude
    comp_lat = self.latitude
    comp_lon = self.longitude
    R = 6373.0

    lat1 = radians(current_lat)
    lon1 = radians(current_lon)
    lat2 = radians(comp_lat)
    lon2 = radians(comp_lon)

    dlon = lon2 - lon1
    dlat = lat2 - lat1

    a = sin(dlat / 2)**2 + cos(lat1) * cos(lat2) * sin(dlon / 2)**2
    c = 2 * atan2(sqrt(a), sqrt(1 - a))

    distance = R * c
    return(distance)

我从 get_distance() 函数中获得了用户位置和公司位置之间的距离。但是如何按升序对距离进行排序? 由于距离与用户的不同位置不同,我无法将距离存储在数据库中。 我想打印按距离升序排序的对象

【问题讨论】:

标签: django


【解决方案1】:

最好的解决方案是研究使用 GeoDjango,它使您能够做到spatial queries

除了您可以做的一些其他事情distance lookups 这主要是您正在寻找的事情。然后,所有查询功能都使用适当的数据库扩展名驻留在数据库中(例如,PostGIS 用于 postgres)。如果您无法使用 GeoDjango 进行原始 SQL 查询,请参阅this question

【讨论】:

    【解决方案2】:

    由于这个问题仍然没有被接受的答案,并且已经有几十个小伙伴看到了,所以我决定试一试。

    首先,我认为 BernhardVallant 和 mrfackowiak 已经指出了正确的解决方案。我将仅说明它的代码可能是什么样子。

    将 Geodjango 添加到您的项目中

    来自官方文档“GeoDjango 旨在成为世界级的地理 Web 框架”。基本上,它使您能够以各种方式(计算距离、根据地理形状过滤对象等)操作地理数据(坐标、栅格、项目)。

    设置它需要几个步骤,许多人已经彻底解释过了。您可以从official documentation's tutorial 开始。

    更新你的模型

    首先,导入 GeoDjango 的模型和地理数据的特殊对象。然后,使用以下更改更新您的模型。

    # models.py
    from django.contrib.gis.db import models
    from django.contrib.gis.geos import GEOSGeometry, fromstr
    
    # Company model inherits from GeoDjango's model
    class Company(models.Model): 
    
        ...  # your other fields go here
        latitude = models.FloatField()
        longitude = models.FloatField()
        geo_location = models.PointField(null=True) # New field
    
        # New method to generate geo_location from lat, lng
        def create_geo_location(self):
            self.geo_location = fromstr(f'POINT({self.lng} {self.lat})', srid=4326)
    
        # Overwrite save() to use create_geo_location()
        def save(self, **kwargs):
            """ Creates a geo_location value (Point), if no prior-value exist"""
            if not self.geo_location:
                self.create_geo_location()
    

    您不会在这里使用 get_distance() 方法,而是将逻辑移动到您的视图中。

    更新你的views.py

    您的视图应该是这样的:

    # views.py
    from <your-app>.models import Company
    from decimal import Decimal
    from django.contrib.gis.geos import fromstr
    from django.contrib.gis.db.models.functions import Distance 
    from django.contrib.gis.measure import D 
    
    class CompanyListView(ListView):
        context_object_name = 'companies'
    
        # Get user lat and lng using the logic in your get_distance() method and 
        # .. transom the values to Decimal 
        # ex. user_lat, user_lng = Decimal(45.5260525), Decimal(-73.5596788) 
    
        # user location is a geographic point value with a specific projection (srid)
        user_location = fromstr(f'POINT({user_lng} {user_lat})', srid=4326)
    
        queryset = Company.objects.filter(geo_location__dwithin=(user_location, D(m=2000)))  
           .annotate(distance_to_user = Distance("geo_location", user_location)) 
           .order_by("distance_to_user") 
    

    查询将获取距离用户 2km 范围内的所有 Company 实例。它还将创建一个带有注释的新变量,称为 distance_to_user ,它将存储距离(以米为单位)。最后,它会对结果进行排序。

    关于地理数据和查询的一些细节我没有解释,但如果你打算使用 GeoDjango,你会更好地了解它们。我希望这会有所帮助。

    【讨论】: