1. 개요
- 목적: 사용자가 사전에 저장한 북마크 위치 반경 10m 내로 진입하면 도착 이벤트를 발생시키는 시스템 구현
- 기술 스택:
- 서버: NestJS, WebSocket
- 클라이언트: Chrome, Kakao Maps API
- 데이터베이스: Valkyrie (발키)
2. 주요 기능 및 흐름
1) 북마크 저장
- 사용자가 특정 위치를 북마크로 저장하면 해당 좌표 (위도, 경도)를 Valkyrie DB에 저장
2) 사용자 위치 추적
- 클라이언트에서 정기적으로 (setInterval) 위치 정보를 가져옴
- Kakao Maps API 또는 Geolocation API를 활용하여 사용자의 현재 위치 확인
3) 서버와 위치 데이터 통신
- 클라이언트 → 서버: WebSocket을 통해 현재 위치를 주기적으로 전송
- 서버 → 클라이언트: 사용자의 위치가 북마크 반경 10m 내에 들어오면 도착 이벤트를 전송
4) 도착 여부 판별 로직
- 서버에서 사용자 위치와 북마크 위치 간 거리 계산
- 거리 계산 공식을 사용하여 반경 10m 이내 여부 확인
- 도착 시 WebSocket을 통해 클라이언트에 이벤트 전송
3. 기술 상세 및 코드 예시
1) 거리 계산 (Haversine 공식 사용)
function getDistance(lat1: number, lon1: number, lat2: number, lon2: number): number {
const R = 6371e3; // 지구 반지름 (미터)
const φ1 = (lat1 * Math.PI) / 180;
const φ2 = (lat2 * Math.PI) / 180;
const Δφ = ((lat2 - lat1) * Math.PI) / 180;
const Δλ = ((lon2 - lon1) * Math.PI) / 180;
const a = Math.sin(Δφ / 2) * Math.sin(Δφ / 2) +
Math.cos(φ1) * Math.cos(φ2) *
Math.sin(Δλ / 2) * Math.sin(Δλ / 2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
return R * c; // 결과값: 미터(m)
}
2) WebSocket을 이용한 위치 업데이트 (클라이언트)
const socket = new WebSocket("ws://your-server.com");
navigator.geolocation.watchPosition(
(position) => {
const { latitude, longitude } = position.coords;
socket.send(JSON.stringify({ latitude, longitude }));
},
(error) => console.error(error),
{ enableHighAccuracy: true }
);
socket.onmessage = (event) => {
const data = JSON.parse(event.data);
if (data.status === "ARRIVED") {
alert("북마크 위치에 도착했습니다!");
}
};
3) NestJS WebSocket 서버
import { WebSocketGateway, SubscribeMessage, MessageBody, WebSocketServer } from '@nestjs/websockets';
import { Server } from 'socket.io';
@WebSocketGateway()
export class LocationGateway {
@WebSocketServer()
server: Server;
private bookmarks = [
{ id: 1, lat: 37.5665, lon: 126.978, radius: 10 } // 예제 데이터 (서울)
];
@SubscribeMessage('locationUpdate')
handleLocationUpdate(@MessageBody() data: { latitude: number; longitude: number }) {
const { latitude, longitude } = data;
for (const bookmark of this.bookmarks) {
const distance = getDistance(latitude, longitude, bookmark.lat, bookmark.lon);
if (distance <= bookmark.radius) {
this.server.emit('locationEvent', { status: 'ARRIVED', location: bookmark });
break;
}
}
}
}
4. 예상되는 오류 및 해결 방법
예상 오류 | 원인 | 해결 방법 |
GPS 오차로 인해 10m 이내인데 이벤트가 발생하지 않음 | GPS는 5~20m 오차 발생 가능 | 반경을 15m로 설정하거나 GPS 정확도 향상 옵션 (enableHighAccuracy: true) 사용 |
클라이언트 위치 업데이트가 너무 잦아 서버 부하 발생 | watchPosition이 과도하게 실행됨 | 위치 변화 감지 간격 조정 (setInterval로 최소 간격 설정) |
WebSocket 연결이 끊어짐 | 네트워크 불안정 또는 서버 다운 | 재연결 로직 추가 (socket.onclose에서 자동 재연결) |
여러 개의 북마크를 관리할 때 충돌 발생 | 여러 북마크와의 거리 비교 미흡 | 북마크별로 개별 거리 계산 후 가장 가까운 북마크 기준으로 이벤트 처리 |
5. 결론
- NestJS와 WebSocket을 활용하여 실시간 위치 이벤트 감지를 구현
- GPS 오차를 고려하여 반경을 조정하거나 정확도를 향상시키는 방식 적용
- WebSocket 연결 관리 및 최적화된 거리 계산을 통해 성능 이슈 해결
- 이 시스템을 기반으로 향후 퀘스트 자동 완료, 보상 지급 기능 등을 추가 가능
카카오맵 API 으로 지오펜싱(Geofencing) 기능 구현하기
일반적으로 지오펜싱은 특정 위치 반경 내에 사용자가 진입하거나 이탈했을 때 이벤트를 트리거하는 기능입니다.
1. 특정 지점과 현재 위치 간 거리 계산
카카오맵 API에서 제공하는 Coord 객체와 Coord.distance 메서드를 이용하면, 특정 좌표와 사용자의 위치 간 거리를 계산할 수 있습니다.
📌 예제 코드 (반경 100m 이내 감지)
// 사용자의 현재 위치 (예제 값)
let userLat = 37.5665;
let userLng = 126.9780;
// 지정된 지오펜싱 중심 좌표
let targetLat = 37.5670;
let targetLng = 126.9785;
// 좌표 객체 생성
let userPos = new kakao.maps.LatLng(userLat, userLng);
let targetPos = new kakao.maps.LatLng(targetLat, targetLng);
// 두 지점 사이 거리 계산 (미터 단위)
let distance = kakao.maps.geometry.computeDistance(userPos, targetPos);
console.log("현재 위치와 목표 지점 간 거리:", distance, "m");
// 반경 100m 이내인지 확인
if (distance <= 100) {
alert("목표 지점 반경 100m 이내에 도착했습니다!");
}
✅ computeDistance() 함수를 사용하여 거리 계산을 수행하고, 일정 반경 내에 들어왔을 때 이벤트를 트리거할 수 있습니다.
2. 실시간 위치 추적하여 지오펜싱 감지
사용자가 이동할 때마다 지오펜싱을 감지하려면, HTML5의 navigator.geolocation.watchPosition()을 활용하면 됩니다.
📌 예제 코드 (실시간 위치 추적)
navigator.geolocation.watchPosition(
function (position) {
let userLat = position.coords.latitude;
let userLng = position.coords.longitude;
let userPos = new kakao.maps.LatLng(userLat, userLng);
let targetPos = new kakao.maps.LatLng(37.5670, 126.9785);
let distance = kakao.maps.geometry.computeDistance(userPos, targetPos);
console.log("현재 거리:", distance, "m");
if (distance <= 100) {
console.log("목표 지점 반경 100m 이내 진입!");
}
},
function (error) {
console.error("위치 정보를 가져오는 데 실패했습니다.", error);
},
{
enableHighAccuracy: true,
maximumAge: 10000,
timeout: 5000
}
);
✅ 사용자의 위치가 갱신될 때마다 지오펜싱 여부를 체크합니다.
3. 다각형 지오펜싱 (다중 좌표)
원형 지오펜싱(반경) 대신, 다각형 지오펜싱을 설정하고 특정 지역 안에 사용자가 있는지 확인할 수도 있습니다.
📌 예제 코드 (다각형 내부 확인)
// 지오펜싱 영역 설정 (다각형 좌표 리스트)
let geofenceCoords = [
new kakao.maps.LatLng(37.5671, 126.9775),
new kakao.maps.LatLng(37.5675, 126.9780),
new kakao.maps.LatLng(37.5670, 126.9790),
new kakao.maps.LatLng(37.5665, 126.9785)
];
// 다각형 생성
let polygon = new kakao.maps.Polygon({
path: geofenceCoords
});
// 사용자의 현재 위치 (예제 값)
let userPos = new kakao.maps.LatLng(37.5672, 126.9782);
// 사용자가 다각형 내부에 있는지 확인
if (kakao.maps.Polygon.contains(polygon, userPos)) {
console.log("사용자가 지오펜싱 내에 있습니다!");
} else {
console.log("사용자가 지오펜싱 바깥에 있습니다.");
}
✅ kakao.maps.Polygon.contains() 함수를 사용하면 다각형 내부에 사용자가 있는지 확인할 수 있습니다.
📌 정리
기능 | 구현방법 |
반경 기반 지오펜싱 | computeDistance()로 거리 측정 |
실시간 위치 추적 | navigator.geolocation.watchPosition() 사용 |
다각형(폴리곤) 기반 지오펜싱 | kakao.maps.Polygon.contains() 활용 |
10초마다 Redis(벨키)에 업데이트되는 사용자 위치 정보를 활용하여 지오펜싱을 구현하는 방법. (백엔드 : nestJs)
📌 0. 시스템 개요
- 프론트엔드 (카카오맵 API)
- navigator.geolocation.watchPosition()을 이용해 10초마다 사용자의 위치를 갱신
- 위치 데이터를 NestJS API에 전송
- 백엔드 (NestJS + Redis)
- 사용자 위치를 Redis에 저장 (SET user:{id}:location lat,lng)
- 주기적으로 Redis에 저장된 위치를 불러와 특정 지점과 거리 비교
- 지오펜싱 영역 내 진입 시 이벤트 발생 (DB 저장, 알림 전송 등)
📌 1. 주기적으로 사용자 위치 확인 (지오펜싱 적용)
10초마다 Redis에 저장된 유저 위치를 확인하고, 특정 지점 반경 내에 들어오면 이벤트를 발생시키는 작업을 한다.
📌 특정 좌표와 거리 비교 (location.service.ts 수정)
import * as haversine from 'haversine-distance';
const TARGET_LOCATION = { lat: 37.5670, lng: 126.9785 }; // 지오펜싱 타겟 위치
const RADIUS = 100; // 반경 100m
async checkGeofencing(userId: string) {
const userLocation = await this.getUserLocation(userId);
if (!userLocation) return false;
const distance = haversine(TARGET_LOCATION, userLocation);
console.log(`사용자 ${userId} 거리: ${distance}m`);
if (distance <= RADIUS) {
console.log(`✅ 사용자 ${userId}가 지오펜싱 영역 내에 있습니다!`);
return true;
}
return false;
}
✅ haversine-distance 패키지를 이용해 거리 계산 (npm install haversine-distance)
📌 2. 크론 작업으로 자동 검사 (geofencing.task.ts)
NestJS에서 @nestjs/schedule을 사용하면 일정 주기로 Redis의 사용자 위치를 확인할 수 있다.
📌 @nestjs/schedule 설치
npm install @nestjs/schedule
📌 주기적 작업 추가 (geofencing.task.ts)
import { Injectable } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
import { LocationService } from './location.service';
@Injectable()
export class GeofencingTask {
constructor(private readonly locationService: LocationService) {}
@Cron('*/10 * * * * *') // 10초마다 실행
async checkUserLocations() {
const userIds = ['123', '456']; // Redis에서 전체 유저 리스트를 가져오는 방식도 가능
for (const userId of userIds) {
const isInFence = await this.locationService.checkGeofencing(userId);
if (isInFence) {
console.log(`🎯 ${userId}이(가) 지오펜싱 내부에 있습니다!`);
// 여기에 알림 전송 로직 추가 가능 (Firebase, WebSocket 등)
}
}
}
}
✅ 10초마다 Redis에 저장된 사용자 위치를 검사하여 지오펜싱 범위 내에 있는지 확인
📌 3. 정리
단계 | 구현 방법 |
1. 프론트엔드 | 10초마다 위치 전송 (navigator.geolocation.getCurrentPosition) |
2. 백엔드 저장 | NestJS + Redis로 사용자 위치 저장 |
3. 지오펜싱 검사 | Haversine 거리 계산 후 반경 내 여부 확인 |
4. 자동 실행 | @nestjs/schedule로 10초마다 검사 |
5. 알림 | WebSocket / Firebase로 알림 전송 가능 |
➡️ NestJS + Redis를 활용하면 실시간 지오펜싱을 효율적으로 구현할 수 있음 🚀
참고하면 좋을 링크
재능- 실시간 위치 기반 서비스: 지오펜싱과 맵 API 활용: https://www.jaenung.net/tree/7647
우아한 기술 블로그 - Hello, Geo-fence! : https://techblog.woowahan.com/2567/
'게임서버-스파르타코딩NodeJs_7기 > CH6 최종 프로젝트' 카테고리의 다른 글
사용자와 북마커 계획 (0) | 2025.02.26 |
---|---|
사용자위치와 다수의 북마커 관리 (0) | 2025.02.26 |
발키에서 복수 데이터 읽어오기 (0) | 2025.02.25 |
마커로 길찾기 (0) | 2025.02.20 |
html에서 카카오 지도 api 사용하기 (0) | 2025.02.19 |