J'ai besoin de répertorier les magasins à proximité d'une adresse Voici mon code:
class NearbyShops(APIView): permission_classes = [IsAuthenticated] serializer_class = NearbyShopsSerializer def post(self, request): data = request.data serializer = NearbyShopsSerializer(data=data) if serializer.is_valid(raise_exception=True): try: address = DeliveryAddress.objects.get(client=request.user, lat=serializer.data.get('address_lat'), long=serializer.data.get('address_long')) except DeliveryAddress().DoesNotExist: return Response({"error": "This address doesn't exist"}, status=status.HTTP_404_NOT_FOUND) try: shops = Shop.objects.filter(city=address.city) except Shop().DoesNotExist: return Response({"error": "No shops in this city address"}, status=status.HTTP_417_EXPECTATION_FAILED) list = {} for shop in shops: location = (address.lat, address.long) dis = shop.distance_shop(location) shops = shops.annotate(distance=dis).order_by('distance') closest = Shop.objects.filter(distance__lt=10.0) for close in closest: list['name'] = close.name list['long'] = close.long list['lat'] = close.lat return Response({'shops': list}, status=status.HTTP_200_OK)
####################
#this is the function I used for calculating distance I used haversine distance(origin, destination) def distance(origin, destination): lat1, lon1 = origin lat2, lon2 = destination radius = 6371 # km dlat = math.radians(lat2 - lat1) dlon = math.radians(lon2 - lon1) a = math.sin(dlat / 2) * math.sin(dlat / 2) + math.cos(math.radians(lat1))* math.cos(math.radians(lat2)) * math.sin(dlon/2)*math.sin(dlon / 2) c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a)) d = radius * c return d
dans mon apiView en utilisant la méthode post recevant une adresse lat et longue j'ai fait ceci:
class Shop(TimeStamp): city = models.CharField(max_length=15, choices=CITIES, blank=True) lat = models.DecimalField(decimal_places=6, max_digits=10, verbose_name='latitude', default=None) long = models.DecimalField(decimal_places=6, max_digits=10, verbose_name='longitude', default=None) def distance_shop(self, location): return distance((self.lat, self.long), location)
Je ne sais pas pourquoi, mais j'obtiens en retour cette erreur: p>
QuerySet.annotate () a reçu des non-expression (s): 4783.728105194982
3 Réponses :
Il est préférable de stocker un géopoint dans votre table plutôt que de stocker la longitude et la latitude séparément. Cela améliorera les performances de votre vue et simplifiera votre code
votre boutique, le modèle d'adresse devrait ressembler à ceci
geopoint = Point(address_long, address_lat) closest_shops = Shop.objects.filter(geopoint__distance_lte=(place.geometry, D(m=10.0)).annotate(distance=Distance('geometry', geopoint))
Ensuite, vous pouvez utiliser quelque chose comme ceci dans votre vue:
class MyModel(models.Model: geopoint = GeometryField(null=True, blank=True)
S'il vous plaît laissez-moi savoir si vous avez besoin de plus de précisions
J'aimerais pouvoir utiliser geopoint et tous les outils postgis, mais je ne peux pas, je dois utiliser la latitude et la longitude séparément, c'est ce que je reçois du point de terminaison du client. J'apprécie votre aide, merci!
la plupart des bases de données prennent en charge les requêtes spatiales, alors essayez de le faire. Je tiens également à souligner que vous pouvez construire le point à partir de ce que vous obtenez du client comme indiqué dans ma slution
Tout d'abord, pendant que vous essayez de créer une application de géolocalisation, vous devez utiliser postgresql
et son extension postgis
, et django a un package puissant nommé geodjango
, vous n'avez plus besoin d'utiliser deux noms de champs différents comme vous fait long
, lat
from django.views import generic from django.contrib.gis.geos import fromstr , Point from django.contrib.gis.db.models.functions import Distance from .models import Shop Latitude = 31.2551 # for example , you can get long and lat from user using js Longitude = 49.8824 #for example user_location = Point(Latitude , Longitude , srid=4326) class NearestShop(generic.ListView): model = Shop context_object_name = 'shops' queryset = Shop.objects.annotate(distance=Distance('long_lat ',user_location)).order_by('distance').distinct('distance')
puis dans vos vues.py
from django.contrib.gis.db import models class Shop(models.Model): city= models.CharField(max_length=100) long_lat = models.PointField()
si vous utilisez Windows et que vous ne pouvez pas configurer postgis
, je vous encourage à utiliser la ligne de commande ubuntu sur Windows
Merci de votre aide. Mais dans mon cas, je ne peux pas du tout utiliser pstgis. Je sais si bien à quel point geodjango est utile, mais je n'ai qu'à faire du calcul géographique moi-même,
alors utilisez la requête que j'ai écrite
votre requête nécessite l'importation de SIG, et je ne suis pas en mesure de le faire nécessite de nombreuses bibliothèques qui ralentiront mon application
J'ai fini par faire cela au lieu d'utiliser annotate (distance). sans utiliser l'extension Postgis.
#### see code in Question list = {} location = (address.lat, address.long) for shop in shops: dis = shop.distance_shop(location) if dis <= 10.0: list['name'] = shop.name list['long'] = shop.long list['lat'] = shop.lat return Response({'message': 'no close shops to your address'}, status=status.HTTP_303_SEE_OTHER) return Response({'shops in district': list}, status=status.HTTP_200_OK)