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

Tri*_*lla 4 html python sqlite django django-models

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

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

我有GeoIP2名单与citycountry在我的项目文件夹中,我不知道我该如何使用他们,但他们似乎是一个不错的选择,因为他们采取latlon坐标,并产生出一个位置。

我知道这里有几个选项,例如pointlat lon坐标中创建一个值,然后也许可以按半径过滤?等等,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')
Run Code Online (Sandbox Code Playgroud)

位置 page.html 示例

{% for user in location %}
<h5><b>{{ user.first_name }} {{ user.last_name }}</b></h5>
{% endfor %}
Run Code Online (Sandbox Code Playgroud)

Dam*_*hev 5

首先,如果您的用户只有一个位置,则更有可能使用 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'
Run Code Online (Sandbox Code Playgroud)

请注意, null=False 是相关字段的默认值。现在我们需要计算半径的平方作为纬度和经度之间差异的平方和。对于每一行的计算,我们可以使用 django.models.F 类在 annotate 或 filter 方法中使用。最后,我们在 filter 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)
Run Code Online (Sandbox Code Playgroud)

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

请注意,此计算在远距离上会不准确,而在近极地区会更不准确。在半径附近抓住这个9系数?它是赤道上一度的千米数)。为了更准确,我们需要更深入地学习数学并根据用户的纬度使用计算出的系数。

需要更高的准确性?只需使用 GeoDjango 或像谷歌地理编码这样的侧面 API。