180 lines
6.5 KiB
Python
180 lines
6.5 KiB
Python
import math
|
|
|
|
from django.shortcuts import render
|
|
from django.db.models import F, ExpressionWrapper, FloatField
|
|
from django.db.models.functions import Sqrt, Power, Sin, Cos, Radians, ATan2
|
|
from rest_framework import generics, filters, permissions
|
|
from django_filters.rest_framework import DjangoFilterBackend
|
|
import logging
|
|
|
|
from location import models, serializers
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
# PROVINCES
|
|
class ProvinceList(generics.ListCreateAPIView):
|
|
queryset = models.Province.objects.all()
|
|
serializer_class = serializers.ProvinceSerializer
|
|
filter_backends = [filters.SearchFilter, filters.OrderingFilter, DjangoFilterBackend]
|
|
search_fields = ['name', 'code']
|
|
filterset_fields = ['name', 'code']
|
|
ordering_fields = '__all__'
|
|
|
|
def get_permissions(self):
|
|
if self.request.method == 'POST':
|
|
return [permissions.IsAdminUser()]
|
|
return [permissions.IsAuthenticated()]
|
|
|
|
class ProvinceDetail(generics.RetrieveUpdateDestroyAPIView):
|
|
queryset = models.Province.objects.all()
|
|
serializer_class = serializers.ProvinceSerializer
|
|
|
|
def get_permissions(self):
|
|
if self.request.method == 'PUT' or self.request.method == 'DELETE':
|
|
return [permissions.IsAdminUser()]
|
|
return [permissions.IsAuthenticated()]
|
|
|
|
# REGENCY/CITY
|
|
|
|
class RegencyCityList(generics.ListCreateAPIView):
|
|
queryset = models.RegencyCity.objects.all()
|
|
serializer_class = serializers.RegencyCitySerializer
|
|
filter_backends = [filters.SearchFilter, filters.OrderingFilter, DjangoFilterBackend]
|
|
search_fields = ['name', 'code']
|
|
filterset_fields = ['name', 'code', 'province']
|
|
ordering_fields = '__all__'
|
|
|
|
def get_serializer_class(self):
|
|
serializer_class = self.serializer_class
|
|
|
|
if self.request.method == 'GET':
|
|
serializer_class = serializers.RegencyCityDetailSerializer
|
|
|
|
return serializer_class
|
|
|
|
def get_permissions(self):
|
|
if self.request.method == 'POST':
|
|
return [permissions.IsAdminUser()]
|
|
return [permissions.IsAuthenticated()]
|
|
|
|
class RegencyCityDetail(generics.RetrieveUpdateDestroyAPIView):
|
|
queryset = models.RegencyCity.objects.all()
|
|
serializer_class = serializers.RegencyCitySerializer
|
|
|
|
def get_serializer_class(self):
|
|
serializer_class = self.serializer_class
|
|
|
|
if self.request.method == 'GET':
|
|
serializer_class = serializers.RegencyCityDetailSerializer
|
|
|
|
return serializer_class
|
|
|
|
def get_permissions(self):
|
|
if self.request.method == 'PUT' or self.request.method == 'DELETE':
|
|
return [permissions.IsAdminUser()]
|
|
return [permissions.IsAuthenticated()]
|
|
|
|
# LOCATION
|
|
class LocationList(generics.ListCreateAPIView):
|
|
queryset = models.Location.objects.all()
|
|
serializer_class = serializers.LocationSerializer
|
|
filter_backends = [filters.SearchFilter, filters.OrderingFilter, DjangoFilterBackend]
|
|
search_fields = ['name']
|
|
filterset_fields = ['name', 'totem_code', 'province', 'regency_city', 'active']
|
|
ordering_fields = '__all__'
|
|
|
|
def get_queryset(self):
|
|
queryset = models.Location.objects.all()
|
|
|
|
lat = self.request.query_params.get('lat')
|
|
lng = self.request.query_params.get('lng')
|
|
radius = self.request.query_params.get('radius')
|
|
|
|
if lat and lng:
|
|
lat = float(lat)
|
|
lng = float(lng)
|
|
radius = float(radius) if radius else 50 # default 50m
|
|
|
|
min_lat, max_lat, min_lng, max_lng = get_bounding_box(lat, lng, radius)
|
|
|
|
logger.info(f"Filtering locations within radius {radius}m of point ({lat}, {lng})")
|
|
|
|
# Step 1: bounding box filter
|
|
queryset = queryset.filter(
|
|
totem_latitude__range=(min_lat, max_lat),
|
|
totem_longitude__range=(min_lng, max_lng)
|
|
)
|
|
|
|
# Step 2: haversine formula
|
|
distance_expr = ExpressionWrapper(
|
|
6371000 * 2 * ATan2(
|
|
Sqrt(
|
|
Power(Sin((Radians(F('totem_latitude')) - Radians(lat)) / 2), 2) +
|
|
Cos(Radians(lat)) *
|
|
Cos(Radians(F('totem_latitude'))) *
|
|
Power(Sin((Radians(F('totem_longitude')) - Radians(lng)) / 2), 2)
|
|
),
|
|
Sqrt(1 - (
|
|
Power(Sin((Radians(F('totem_latitude')) - Radians(lat)) / 2), 2) +
|
|
Cos(Radians(lat)) *
|
|
Cos(Radians(F('totem_latitude'))) *
|
|
Power(Sin((Radians(F('totem_longitude')) - Radians(lng)) / 2), 2)
|
|
))
|
|
),
|
|
output_field=FloatField()
|
|
)
|
|
|
|
queryset = queryset.annotate(
|
|
distance=distance_expr
|
|
).filter(
|
|
distance__lte=radius
|
|
).order_by('distance')
|
|
|
|
logger.info(f"Queryset after applying bounding box and distance filter: {queryset.query}")
|
|
logger.info(f"Applied bounding box filter: lat between {min_lat} and {max_lat}, lng between {min_lng} and {max_lng}")
|
|
logger.info(f"Found {queryset.count()} locations within radius {radius}m of point ({lat}, {lng})")
|
|
|
|
return queryset
|
|
|
|
def get_serializer_class(self):
|
|
serializer_class = self.serializer_class
|
|
|
|
if self.request.method == 'GET':
|
|
serializer_class = serializers.LocationDetailSerializer
|
|
|
|
return serializer_class
|
|
|
|
def get_permissions(self):
|
|
if self.request.method == 'POST':
|
|
return [permissions.IsAdminUser()]
|
|
return [permissions.IsAuthenticated()]
|
|
|
|
class LocationDetail(generics.RetrieveUpdateDestroyAPIView):
|
|
queryset = models.Location.objects.all()
|
|
serializer_class = serializers.LocationSerializer
|
|
|
|
def get_serializer_class(self):
|
|
serializer_class = self.serializer_class
|
|
|
|
if self.request.method == 'GET':
|
|
serializer_class = serializers.LocationDetailSerializer
|
|
|
|
return serializer_class
|
|
|
|
def get_permissions(self):
|
|
if self.request.method == 'PUT' or self.request.method == 'DELETE':
|
|
return [permissions.IsAdminUser()]
|
|
return [permissions.IsAuthenticated()]
|
|
|
|
def get_bounding_box(lat, lng, radius_in_meters):
|
|
earth_radius = 6371000 # meter
|
|
|
|
lat_delta = (radius_in_meters / earth_radius) * (180 / math.pi)
|
|
lng_delta = (radius_in_meters / earth_radius) * (180 / math.pi) / math.cos(lat * math.pi/180)
|
|
|
|
return (
|
|
lat - lat_delta,
|
|
lat + lat_delta,
|
|
lng - lng_delta,
|
|
lng + lng_delta
|
|
) |