Post

⚓ DATT 09 - 좌표 기반 Nearby Search API 설계 및 위치 기반 검색 시스템 구현

⚓ DATT 09 - 좌표 기반 Nearby Search API 설계 및 위치 기반 검색 시스템 구현

개요

DATT는 단순 장소 검색 서비스가 아니다.

핵심 목표는:

1
2
특정 위치를 기준으로
주변 경험을 큐레이션하는 플랫폼

을 만드는 것이다.

즉:

1
2
3
4
현재 위치
→ 주변 장소 탐색
→ Anchor 생성
→ 친구 공유

흐름을 가진다.

따라서:

1
위치 기반 검색

은 DATT의 핵심 기능 중 하나다.

이번 단계에서는:

1
Nearby Search API

를 구현하며:

1
2
3
좌표 기반 반경 검색
거리 계산
거리순 정렬

구조를 설계하였다.


왜 위치 기반 검색이 중요한가

기존 장소 검색은 대부분:

1
2
3
키워드
카테고리
지역명

기반이다.

하지만 실제 사용자 경험은 다르다.

예를 들어 사용자는:

1
2
3
"지금 내 주변 카페"
"현재 위치 기준 술집"
"강남역 근처 놀거리"

를 더 많이 찾는다.

즉 검색 기준이:

1
2
문자열
→ 좌표

로 이동한다.

DATT의 Anchor 기능 역시:

1
2
특정 위치 기준
→ 주변 맛집/술집/숙소/놀거리 큐레이션

구조이므로 위치 기반 검색이 반드시 필요하다.


Nearby Search API 설계

이번 단계에서 설계한 API는 다음과 같다.

1
GET /api/places/nearby

요청 파라미터

1
2
3
4
5
6
7
lon
lat
radiusKm
keyword
indsMclsCd
page
size

요청 예시

1
GET /api/places/nearby?lon=127.0276&lat=37.4979&radiusKm=3

업종 필터 포함

1
GET /api/places/nearby?lon=127.0276&lat=37.4979&radiusKm=3&indsMclsCd=I212

키워드 포함

1
GET /api/places/nearby?lon=127.0276&lat=37.4979&radiusKm=3&keyword=스타벅스

Nearby 검색 정책

현재 MVP 기준 Nearby 검색 정책은 다음과 같다.

1
2
3
4
5
반경 내 장소만 조회
거리순 정렬
업종 필터 optional
키워드 검색 optional
Pageable 지원

즉 기본 정렬은:

1
distance ASC

고정이다.


위치 기반 검색 시스템의 핵심 개념

위치 기반 검색 시스템에서 가장 중요한 것은:

1
두 좌표 사이 거리 계산

이다.

즉:

1
현재 위치 ↔ 장소 위치

사이 거리를 계산해야 한다.


좌표 데이터

위치 데이터는 보통 다음 형태를 가진다.

1
2
위도(latitude)
경도(longitude)

예:

1
2
lat = 37.4979
lon = 127.0276

위치 기반 검색 핵심 흐름

전체 흐름은 다음과 같다.

1
2
3
4
5
현재 위치 입력
→ 각 장소와 거리 계산
→ 반경 내 장소 필터링
→ 거리순 정렬
→ 반환

즉 핵심은:

1
거리 계산 + 반경 필터링

이다.


Haversine Formula 기반 거리 계산 구현

현재 MVP에서는:

1
Haversine Formula

를 사용하였다.


Haversine Formula란?

지구는 완전한 평면이 아니다.

따라서 단순 피타고라스 거리 계산으로는 정확도가 떨어진다.

Haversine Formula는:

1
구면 거리 계산 공식

으로 위도/경도 기반 실제 거리를 계산할 수 있다.


거리 계산 유틸 구현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public final class DistanceCalculator {

    private static final double EARTH_RADIUS_KM = 6371.0;

    private DistanceCalculator() {
    }

    public static double calculateDistanceKm(
        double baseLat,
        double baseLon,
        double targetLat,
        double targetLon
    ) {
        double baseLatRad = Math.toRadians(baseLat);
        double targetLatRad = Math.toRadians(targetLat);

        double deltaLat = Math.toRadians(targetLat - baseLat);
        double deltaLon = Math.toRadians(targetLon - baseLon);

        double a = Math.sin(deltaLat / 2) * Math.sin(deltaLat / 2)
            + Math.cos(baseLatRad)
            * Math.cos(targetLatRad)
            * Math.sin(deltaLon / 2)
            * Math.sin(deltaLon / 2);

        double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));

        return EARTH_RADIUS_KM * c;
    }
}

거리 계산 결과

예:

1
강남 ↔ 홍대

거리 계산 시:

1
약 11km 전후

수준 결과가 나온다.


QueryDSL 기반 거리 계산 구현

단순 Java 계산만으로는 Nearby 검색을 할 수 없다.

왜냐하면:

1
2
DB의 모든 장소를 가져온 뒤
Java에서 거리 계산

하면 매우 비효율적이기 때문이다.

따라서 이번 단계에서는:

1
DB Query 내부에서 거리 계산

하도록 구현하였다.


QueryDSL distanceExpression

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
private NumberExpression<Double> distanceExpression(
    Double baseLat,
    Double baseLon
) {
    return Expressions.numberTemplate(
        Double.class,
        """
        (6371 * acos(
            cos(radians({0})) * cos(radians({1})) *
            cos(radians({2}) - radians({3})) +
            sin(radians({0})) * sin(radians({1}))
        ))
        """,
        baseLat,
        placeMaster.lat,
        placeMaster.lon,
        baseLon
    );
}

Nearby 검색 Query 구조

1
2
3
4
5
6
.where(
    withinRadius(distanceExpression, condition.getRadiusKm()),
    keywordContains(condition.getKeyword()),
    indsMclsCdEq(condition.getIndsMclsCd())
)
.orderBy(distanceExpression.asc())

Nearby Search 핵심 흐름

최종 Nearby Query 흐름은 다음과 같다.

1
2
3
4
5
거리 계산
→ 반경 조건 필터링
→ 키워드 조건 적용
→ 업종 조건 적용
→ 거리순 정렬

왜 PostGIS를 지금 도입하지 않았는가

위치 기반 서비스를 만들면 가장 먼저 나오는 이야기가:

1
"PostGIS 써야 하는 거 아닌가?"

이다.

하지만 이번 MVP에서는 PostGIS를 도입하지 않았다.


이유 1. 현재 데이터 규모가 크지 않다

현재 DATT 데이터 규모는:

1
수만 ~ 수십만 수준

이다.

이 정도는:

1
PostgreSQL + QueryDSL

만으로도 충분히 처리 가능하다.


이유 2. 운영 복잡도가 증가한다

PostGIS를 도입하면:

1
2
3
4
공간 타입
공간 인덱스
GIS 함수
운영 복잡도

가 추가된다.

즉 MVP 단계에서는:

1
과도한 복잡도 증가

가 발생한다.


이유 3. 현재 검색 요구사항이 단순하다

현재 Nearby 검색은:

1
2
반경 검색
거리순 정렬

정도만 필요하다.

아직:

1
2
3
고급 공간 분석
경로 탐색
Polygon Search

등은 필요하지 않다.


현재 Nearby 구조의 장점

현재 구조는 다음 장점을 가진다.

1. 단순하다

운영 부담이 낮다.


2. 구현 속도가 빠르다

MVP 개발에 적합하다.


3. 확장 가능하다

향후:

1
2
3
4
PostGIS
GeoHash
Redis GEO
Bounding Box

등으로 확장 가능하다.


향후 고도화 예정

추후 다음 단계에서 성능 개선을 고려할 예정이다.

1
2
3
4
5
PostGIS
공간 인덱스
Bounding Box 최적화
Redis GEO
GeoHash

또한:

1
Anchor 기반 주변 추천

기능도 Nearby API를 기반으로 구현될 예정이다.


Nearby Search 테스트 전략

이번 단계에서는 다음 항목들을 테스트하였다.

1
2
3
4
5
6
반경 내 장소 조회
거리순 정렬
업종 필터링
키워드 검색
좌표 검증
반경 검증

즉:

1
기능 정확성

을 우선 검증하였다.


마무리

이번 단계에서는:

1
위치 기반 Nearby Search API

를 구현하였다.

핵심은:

1
2
3
좌표 기반 거리 계산
반경 필터링
거리순 정렬

이다.

이 Nearby 구조는 이후:

1
2
3
4
Anchor
주변 추천
위치 기반 큐레이션
지도 탐색

등 DATT 핵심 기능의 기반이 된다.

This post is licensed under CC BY 4.0 by the author.