Django 仅使用 Sqlite 按距离过滤(无 GeoDjango、PostGres、MySQL、GIS)

Posted

技术标签:

【中文标题】Django 仅使用 Sqlite 按距离过滤(无 GeoDjango、PostGres、MySQL、GIS)【英文标题】:Django Filter by Distance using only Sqlite (No GeoDjango, PostGres, MySQL, GIS) 【发布时间】:2019-07-01 10:05:49 【问题描述】:

我有一个 Django 应用程序需要按位置过滤用户(显示在登录用户给定距离内的用户)。

现在我可以使用 html5 捕获登录用户 latitude and longitude 坐标并将它们保存到我的 SQLite 后端的 Location 表中。

我的项目文件夹中有带有citycountry 的GeoIP2 列表,我不确定如何使用它们,但它们似乎是一个不错的选择,因为它们包含lat 和@ 987654326@坐标并生成位置。

我知道这里有几个选项,例如从lat lon 坐标创建一个point 值,然后也许可以按半径过滤?等等,5公里,10公里,15公里?

我对所有这些都比较陌生,这就是为什么我要求一个干净的最小解决方案。

我不想使用 PostGres、GIS、GeoDjango。

如果有人有一个使用 Sqlite 和可能的 geoip2 列表的简单解决方案,我非常感谢你分享它!

模型.py

class Location(models.Model):
latitude    = models.DecimalField(max_digits=19, decimal_places=16)
longitude   = models.DecimalField(max_digits=19, decimal_places=16)
user        = models.OneToOneField(User, on_delete=models.CASCADE, null=False, related_name='loc')

def __str__(self):
    return f'self.user\'s location'


@login_required
def insert(request):
    location = Location(latitude=request.POST['latitude'], 
longitude=request.POST['longitude'], user = request.user)
    location.save()
    return JsonResponse('message': 'success')

def location_view(request):
    queryset = User.objects.annotate(
        radius_sqr=pow(models.F('loc__latitude') - 
request.user.loc.latitude, 2) + pow(models.F('loc__longitude') - 
request.user.loc.longitude, 2)
    ).filter(
        radius_sqr__lte=pow(radius_km / 9, 2)
    )
    return django.shortcuts.render_to_response(request, context, 'connect/home.html')

位置 page.html 示例

% for user in location %
<h5><b> user.first_name   user.last_name </b></h5>
% endfor %

【问题讨论】:

【参考方案1】:

首先,如果您的用户只有一个位置,则更有可能使用 OneToOneField 而不是外键。将“related_name”参数放在您用来增强查询构建的任何关系中也是一个好主意。像这样:

    class Location(models.Model):
    lat = models.DecimalField(max_digits=19, decimal_places=16)
    lon = models.DecimalField(max_digits=19, decimal_places=16)
    user = models.OneToOneField('User', on_delete=models.CASCADE, related_name='loc')

    def __str__(self):
        return f'self.user\'s location'

注意,null=False 是相关字段的默认值。 现在我们需要将半径的平方计算为纬度和经度之间差异的平方和。对于每一行的计算,我们可以使用 django.models.F 类在注释或过滤方法中使用。最后,我们在过滤 lte 方法中使用计算值(重要的是按半径的平方过滤)。对于 request.user,它看起来像:

from django.db.models import F
from django.shortcuts import render_to_response

def location_view(request):
    radius_km = request.data.get('radius', 0)
    queryset = User.objects.annotate(
        radius_sqr=pow(models.F('loc__latitude') - 
request.user.loc.latitude, 2) + pow(models.F('loc__longitude') - 
request.user.loc.longitude, 2)
    ).filter(
        radius_sqr__lte=pow(radius_km / 9, 2)
    )
    context = dict(location=queryset)
    return render_to_response('connect/home.html', context)

radius作为数字放在javascript函数的请求POST负载中,这将请求POST到location_view

请注意,此计算在远距离上会不准确,而在近极地地区则更不准确。在半径附近捕捉到这个 9 系数?它是赤道上一度数公里)。为了获得更高的准确性,我们需要更深入地进行数学运算并根据用户的纬度使用计算出的系数。

需要更高的准确性?只需使用 GeoDjango 或 Google 地理编码等辅助 API。

【讨论】:

嗨!非常感谢您的帮助和建议,我已经采纳了他们。我还有几个问题。所以我在def location_view 中添加了queryset(我已经更新了我的问题,所以你可以看到)。渲染 queryset 的 django 模板语法是什么?前任。 % for user in qs %?另外,最后一个问题,是否可以编辑 javascript 函数以在按下 save location 按钮时呈现 queryset 视图?现在它调用def insert_view 保存lat and lon。再次感谢您。 首先,是的,在模板中迭代查询集是一种很好的做法。 根据第二个问题,从 save_location 视图中,您可以返回带有给定模板名称、请求对象和上下文的“django.shortcuts.render_to_response()”,其中包含给定的查询集。 返回location_view 中的查询集的实际语法是什么?我试过% for user in queryset %,但没有渲染。我想确保我正确理解了这一点。我有path('location', connect_views.location_view, name='location_filter') 调用location_viewqueryset = User.objects.annotate etc 对吗? % for user in queryset % 是模板语法的一部分。在您的模板中使用它。您需要将新模板添加到您的项目中。让它成为你的connect/home.html。如果有多种响应,您需要一个基本模板并使用您的位置模板对其进行扩展。 See how. 无论如何,您还需要将templates support 添加到您的项目中。

以上是关于Django 仅使用 Sqlite 按距离过滤(无 GeoDjango、PostGres、MySQL、GIS)的主要内容,如果未能解决你的问题,请参考以下文章

Django 按计算字段排序

在Django rest框架中过滤给定距离内的用户

地理位置:按邻近度过滤

如何在 django 中使用日期时间过滤器仅用于日期和月份?

sqlite3 按最大值查询并按第二个因素过滤

Django 仅基于日期过滤日期时间