GeoDjango: Trouver tous les points dans un rayon?


9

Je travaille dans GeoDjango (avec un backend Django 1.7 et PostGIS 2.1.4). J'ai un modèle avec un PointField dans ma base de données et j'essaie de trouver tous les éléments dans un rayon de 10 km d'un point.

Voici mon code modèle:

class Place(models.Model):
    id = models.IntegerField(primary_key=True)
    location = models.PointField(null=True, blank=True)
    objects = models.GeoManager()

Voici mon code de vue (abrégé pour plus de lisibilité):

    from django.contrib.gis.geos import Point
    from django.contrib.gis.measure import Distance
    lat = 52.5
    lng = 1.0
    radius = 10
    point = Point(lng, lat)
    print point
    places = Place.objects.filter(location__dwithin=(point.location, Distance(km=radius)))

Cela me donne:

AttributeError: 'Point' object has no attribute 'location'

Je vois le point dans la console: POINT (1.0000000000000000 52.5000000000000000).

Comment dois-je structurer la requête différemment?

Si j'essaie simplement utiliser pointplutôt que point.location(selon la documentation dwithin ) je reçois une autre erreur: ValueError: Only numeric values of degree units are allowed on geographic DWithin queries.

MISE À JOUR:

Cela semble fonctionner, mais je ne sais pas si c'est correct:

    radius = int(radius)
    degrees = radius / 111.325
    point = Point(float(lng), float(lat))
    places = Place.objects.filter(location__dwithin=(point, degrees))

Les résultats semblent corrects, mais je ne sais pas si ma conversion en degrés est raisonnable.


Non, ce n'est pas le cas, ce rapport ne fonctionne que pour l'équateur.
Michal Zimmermann

Réponses:


21
from django.contrib.gis.geos import Point
from django.contrib.gis.measure import Distance  


lat = 52.5
lng = 1.0
radius = 10
point = Point(lng, lat)    
Place.objects.filter(location__distance_lt=(point, Distance(km=radius)))

1

Vous pouvez le faire sans GeoDjango si quelqu'un est intéressé, il vous suffit de 2 extensions sur postgres: cube, earthdistance

from django.db.models import Lookup, Field, fields, Func, QuerySet


class LLToEarth(Func):
    function = 'll_to_earth'
    arg_joiner = ', '
    arity = 2  # The number of arguments the function accepts.

    def __init__(self, *expressions, output_field=None, **extra):
        if output_field is None:
            output_field = fields.Field()
        super().__init__(*expressions, output_field=output_field, **extra)


class EarthBox(LLToEarth):
    function = 'earth_box'
    arg_joiner = ', '


@Field.register_lookup
class Near(Lookup):
    lookup_name = 'in_georange'
    operator = '@>'

    def as_sql(self, compiler, connection):
        lhs, lhs_params = self.process_lhs(compiler, connection)
        rhs, rhs_params = self.process_rhs(compiler, connection)
        params = lhs_params + rhs_params
        return '%s @> %s' % (lhs, rhs), params


def filter_in_range(
        queryset: QuerySet,
        latitude: float,
        longitude: float,
        range_in_meters: int,
        latitude_column_name: str='latitude',
        longitude_column_name: str='longitude',
        field_name: str = 'earthbox',
        lookup_exp: str = 'in_georange',
):
    earthbox = {field_name: EarthBox(LLToEarth(latitude, longitude), range_in_meters)}
    lookup = '%s__%s' % (field_name, lookup_exp)
    in_range = {lookup: LLToEarth(latitude_column_name, longitude_column_name)}
    return queryset.annotate(**earthbox).filter(**in_range)
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.