1. 지오펜싱(GeoFencing)이란?
지오펜싱(Geofencing)은 특정 지리적 영역(예: 건물, 공원, 상점 등)에 가상 경계를 설정하고, 사용자의 위치가 이 경계를 출입할 때 이벤트를 발생시키는 기술입니다.
지오펜싱을 통해 다음과 같은 기능을 구현할 수 있습니다.
• 사용자가 특정 지역에 도착하거나 떠날 때 알림 전송
• 매장 근처를 지나갈 때 할인 쿠폰 제공
• 출퇴근 시간에 따라 자동 출석 체크
Django에서는 Django GIS(Geographic Information System)를 사용하여 PostGIS 기반의 공간 데이터를 처리하고 지오펜싱을 구현할 수 있습니다.
Django에서 지오펜싱을 구현하는 이유
자녀의 실시간 위치를 알려주는 앱 개발 중 자녀가 특정 위치를 출입할 때 부모에게 알림을 보내야 했습니다. 부모가 특정 위치를 즐겨찾는 위치로 저장할 수 있었고 자녀가 특정 위치 반경 200M안에 들어왔는지 여부를 체크했어야 했습니다. 이와 같은 문제를 Django GIS, PostGIS를 활용하여 해결할 수 있었습니다.
2. Django에서 지오펜싱 구현을 위한 사전 준비
📌 2.1 Django 프로젝트 설정
지오펜싱을 구현하려면 Django에서 공간 데이터를 다룰 수 있도록 Django GIS를 활성화해야 합니다.
# base.py
INSTALLED_APPS = [
'django.contrib.gis', # GIS 기능 추가
'rest_framework',
'app.location',
'app.geo_fence',
]
📍 데이터베이스(PostGIS) 설정
Django에서는 PostgreSQL의 PostGIS 확장 기능을 사용하여 공간 데이터를 저장합니다.
settings.py에서 PostGIS를 사용할 수 있도록 설정합니다.
DATABASES = {
'default': {
'ENGINE': 'django.contrib.gis.db.backends.postgis',
'NAME': 'geofencing_db',
'USER': 'postgres',
'PASSWORD': 'yourpassword',
'HOST': 'localhost',
'PORT': '5432',
}
}
📌 2.2 필요한 패키지 설치
다음 패키지를 설치해야 합니다.
pip install django django-rest-framework psycopg2 django-cors-headers
PostGIS를 사용하기 위해 PostgreSQL과 PostGIS를 먼저 설치해야 합니다.
# Ubuntu (Linux) 기준
sudo apt update
sudo apt install postgis
3. 지오펜싱을 위한 모델 설계
📌 3.1 Location 모델 (사용자의 현재 위치 저장)
from django.contrib.gis.db import models
from app.common.models import BaseModel
class Location(BaseModel):
user = models.OneToOneField("user.User", on_delete=models.CASCADE)
name = models.CharField(verbose_name="위치 이름", max_length=255, null=True, blank=True)
address = models.CharField(verbose_name="위치 주소", max_length=128, null=True, blank=True)
latitude = models.FloatField(verbose_name="위도")
longitude = models.FloatField(verbose_name="경도")
is_geofence_inside = models.BooleanField(verbose_name="지오펜싱 안에 있는지 여부", default=False)
class Meta:
db_table = "location"
verbose_name = "위치"
verbose_name_plural = verbose_name
저의 경우에는 Location 모델을 생성하여 주기적으로 위도와 경도를 업데이트하는 방식으로 개발하였습니다.
📌 3.2 GeoFence 모델 (지오펜싱 영역 설정)
from django.contrib.gis.db import models
from django.utils import timezone
class GeoFence(models.Model):
user = models.ForeignKey("user.User", verbose_name="유저", on_delete=models.CASCADE)
schedule = models.OneToOneField("schedule.Schedule", verbose_name="일정", on_delete=models.CASCADE)
name = models.CharField(verbose_name="지역 이름", max_length=255)
location = models.PointField(verbose_name="좌표") # 중심 좌표
boundary = models.PolygonField(verbose_name="경계") # 지오펜스 영역
is_active = models.BooleanField(verbose_name="활성화 여부", default=False)
class Meta:
db_table = "geo_fence"
DjangoGIS에서 지원하는 PolygonField와 PointField를 사용하였습니다.
참고 : https://docs.djangoproject.com/en/5.1/ref/contrib/gis/geos/
GEOS API | Django documentation
The web framework for perfectionists with deadlines.
docs.djangoproject.com
4. 지오펜싱 로직 구현 (위치 업데이트 시 검증)
사용자가 위치를 업데이트하면, 해당 위치가 지오펜싱 경계 안에 있는지 확인합니다.
# serializer.py
from datetime import timedelta
from asgiref.sync import async_to_sync
from channels.layers import get_channel_layer
from django.contrib.gis.geos import Point
from django.db import transaction
from django.utils import timezone
from rest_framework import serializers
from api.v1.location.utils import send_geofence_notification
from app.geo_fence.models import GeoFence
from app.location.models import Location
from app.schedule.models import Schedule
class LocationSerializer(serializers.ModelSerializer):
user = serializers.CharField(source="user.username", read_only=True)
class Meta:
model = Location
fields = ["id", "user", "latitude", "longitude", "name", "address", "is_geofence_inside"]
read_only_fields = ["id", "user", "is_geofence_inside"]
def validate(self, attrs):
attrs = super().validate(attrs)
return attrs
@transaction.atomic
def create(self, validated_data):
instance = Location.objects.create(**validated_data, user_id=self.context["view"].kwargs["child_id"])
return instance
@transaction.atomic
def update(self, instance, validated_data):
point = Point(validated_data["longitude"], validated_data["latitude"])
today = timezone.now()
activation_time = today + timedelta(minutes=30)
# 오늘 날짜의 스케쥴에 관련된 지오 펜싱 핉터
current_schedule = Schedule.objects.filter(
start_time__lte=activation_time, end_time__gte=today, user_id=self.context["view"].kwargs["child_id"]
).values_list("id", flat=True)
# 현재 유저가 지오 펜싱 반경에 위치해 있다면 푸시 알림 발송
geo_fences = GeoFence.objects.filter(user=instance.user, schedule_id__in=current_schedule)
for geo_fence in geo_fences:
is_inside_geofence = geo_fence.boundary.contains(point)
if is_inside_geofence:
if not instance.is_geofence_inside and not geo_fence.is_active:
instance.is_geofence_inside = True
geo_fence.is_active = True
geo_fence.save()
send_geofence_notification(geo_fence, "도착")
break
elif not is_inside_geofence:
if instance.is_geofence_inside and geo_fence.is_active:
instance.is_geofence_inside = False
geo_fence.is_active = False
geo_fence.save()
send_geofence_notification(geo_fence, "떠났")
break
if not geo_fences.exists() and instance.is_geofence_inside:
instance.is_geofence_inside = False
instance = super().update(instance, validated_data)
channel_layer = get_channel_layer()
async_to_sync(channel_layer.group_send)(
f"location_group_{instance.id}",
{
"type": "location_message",
"data": "location",
"latitude": instance.latitude,
"longitude": instance.longitude,
},
)
return instance
Django Channels를 활용하여 사용자의 위치가 변경될 때 WebSocket을 통해 실시간 알림을 보내도록 구현했습니다.
5. 지오펜싱 생성
해당 위치의 위도 경도를 기준으로 가상의 지오펜싱을 만들어서 DB에 저장합니다.
def create_geo_fence(longitude, latitude, location_name, user_id, schedules):
center = Point(longitude, latitude)
distance = Distance(km=0.2)
buffer_degrees = (distance.m / 6371000) / (math.pi / 180)
circle = center.buffer(buffer_degrees)
boundary = GEOSGeometry(circle).convex_hull
if isinstance(schedules, list):
geo_fence_list = [
GeoFence(schedule_id=schedule.id, user_id=user_id, location=center, boundary=boundary, name=location_name)
for schedule in schedules
]
GeoFence.objects.bulk_create(geo_fence_list)
else:
GeoFence.objects.create(
schedule_id=schedules.id, user_id=user_id, location=center, boundary=boundary, name=location_name
)
6. 결론 및 배운 점
• Django GIS와 PostGIS를 활용한 공간 데이터 관리 방법을 배울 수 있었습니다.
• 지오펜싱을 활용한 이벤트 트리거를 구현할 수 있었습니다.
• Django Channels를 활용한 실시간 알림 시스템을 구축할 수 있었습니다.