콘텐츠로 이동

MSG-347 격자 계산 규칙 EPSG:5179 전환

티켓: MSG-347 · 작성일: 2026-08-08 · 작성: prd-writer 상태: 검토됨

1. 문제 상황

현재 격자는 위도와 경도를 고정 상수(0.0009도, 0.00115도)로 나누어 만든다. 위경도는 각도 단위라서 같은 각도 차이라도 지역마다 실제 거리가 다르고, 그 결과 셀의 동서 폭이 서울에서 약 101.6m, 부산에서 약 104.7m로 달라진다. 지금 규칙은 "약 100m" 근사일 뿐 정확한 100m × 100m를 보장하지 못한다.

8/8 멘토 멘토링에서 이 방식을 지적받았고, GPS 좌표를 미터 단위 평면 좌표계인 EPSG:5179[^1]로 변환한 뒤 100으로 나누는 방식으로 바꾸기로 결정했다. 서버와 프론트가 같은 변환 규칙을 공유해서 같은 위치에 항상 같은 격자가 나오게 하는 것까지가 이번 결정의 범위다. (멘토링 정리: cf-33685514, cf-33685556)

2. 목적 · 목표

  • 목적: 전국 어디서나 정확한 100m × 100m 격자 위에서 점령과 방문이 판정되고, 프론트가 그리는 격자와 서버가 판정하는 격자가 항상 일치하게 한다.
  • 목표:
  • 같은 위경도 입력에 대해 서버와 프론트가 항상 같은 gridId를 계산한다. 전국 분산 샘플 200건의 교차 검증이 전부 일치하면 달성이다.
  • 셀 한 변이 전국 어디서나 100m다(투영 오차 수준은 제외).
  • 기존 저장 데이터가 전량 새 격자 기준으로 이행되고 영상 유실이 0건이다.
  • 비목표(스코프 제외):
  • grid_id 포맷 변경. "y_x" 문자열을 유지한다. 단일 정수 ID 전환은 기각했다. 뷰포트 조회가 이미 정수 컬럼 범위 스캔(MSG-90 실측)이라 얻을 성능이 측정 불가 수준인 반면, 전 계층 타입 변경과 프론트 계약 파괴 비용이 확실하기 때문이다.
  • 장소 검색 provider 변경. Kakao Local API를 유지한다.
  • 줌 아웃 집계와 클러스터링(H3 등). 멘토링에서 제안됐지만 후속 과제로 분리한다.
  • 프론트 구현 자체. 이 PRD는 백엔드 몫과 프론트에 제공할 검증 자료까지만 다룬다.
  • (2026-08-08 비목표에서 제외) 이 자리에 있던 "좌표 범위 제한을 신설하지 않는다"는 FR-13으로 뒤집었다. 경위는 8절 참조.

3. 기능 요구사항

ID 요구사항 우선순위
FR-1 서버는 업로드 좌표(위경도)를 EPSG:5179 미터 좌표로 변환해 격자를 판정한다. gridX = floor(x/100), gridY = floor(y/100)이다 Must
FR-2 셀 경계는 반열림 구간[^2]이다. 경계선 위의 좌표는 정확히 한 셀에만 속한다(현행 floor 규칙과 같은 방식) Must
FR-3 grid_id는 "{gridY}_{gridX}" 문자열이다(현행 포맷 유지, 값만 새 체계) Must
FR-4 저장되는 격자 중심점과 경계 도형은 새 셀의 경계를 위경도로 되돌린 값이다. 새 셀은 위경도 축과 평행하지 않으므로 경계 도형은 꼭짓점 4점으로 만든다 Must
FR-5 기존 데이터를 전량 이행한다. grids, videos, user_grids, sponsor_ads, mission_grids의 격자 참조를 저장된 원본 좌표에서 새 규칙으로 재계산하고, 영상은 한 건도 잃지 않는다 Must
FR-6 이행 후 user_grids는 각 사용자의 영상이 실제 속한 새 셀 기준으로 재구성된다. 최초 점령 시각은 그 셀에 속한 영상들의 가장 이른 업로드 시각이다. 셀 재편으로 사용자별 점령 격자 수는 달라질 수 있다 Must
FR-7 격자의 행정동 라벨(region_code[^3])은 새 중심점 기준으로 다시 판정한다 Must
FR-8 zone 사각형과 표시명 데이터는 새 격자 인덱스 기준으로 재산출한다. 명명 규칙("{zone명} {행}-{열}", 행은 북단부터 A, 열은 서단부터 1)은 바꾸지 않는다 Must
FR-9 프론트 검증용으로 전국에 분산된 샘플 200건을 제공한다. 각 건은 입력 위경도, gridX, gridY, gridId, 셀 꼭짓점 위경도 4점을 담는다 Must
FR-10 기존 API의 경로, 파라미터, 응답 구조는 바꾸지 않는다. gridId와 gridY/gridX 값만 새 체계 값으로 바뀐다 Must
FR-11 핫구역 집계는 새 격자 기준으로만 한다. 전환 이전에 쌓인 신호는 폐기해도 된다(핫스코어는 근사값이라는 기존 정책 그대로) Should
FR-12 이행으로 점령 격자 수가 줄어도 이미 획득한 뱃지는 회수하지 않는다(정식 배포 전이라 소급 조정 없음) Must
FR-13 서비스 범위 밖 좌표(위도 33~39, 경도 124~132 바깥)의 업로드는 거부된다. 경계값은 허용한다 Must

4. 비기능 요구사항

분류 요구사항
성능 격자 판정은 요청 처리 중 DB 왕복 없이 앱 서버 안에서 끝난다(현행 동등). 뷰포트 조회는 기존 정수 범위 스캔 경로가 그대로라 기존 성능 기준을 유지한다
데이터 정합 서버와 프론트가 같은 좌표계 정의 문자열[^4]을 공유하고, 그 문자열 자체를 계약으로 삼는다. 정의가 조금이라도 다르면 경계 근처 셀이 어긋날 수 있다. 이행 전후로 영상 총수가 같아야 하고, 각 영상의 새 gridId는 원본 좌표에서 재계산한 값과 일치해야 한다
운영 이행은 Flyway[^5] 마이그레이션 1회로 반영한다. 원본 좌표(videos.geom)가 보존되므로 같은 이행을 다시 실행해도 결과가 같다. 정식 출시 전이라 구버전 클라이언트와의 혼재 호환은 요구하지 않되, dev 환경에서 프론트 전환과 배포 시점을 맞춘다

5. 시퀀스 다이어그램

업로드 시 격자 판정(변경 후). 굵은 부분만 바뀌고 나머지 흐름은 현행 그대로다.

시퀀스 다이어그램
시퀀스 다이어그램

6. 클래스 다이어그램

신규 타입 없이 기존 두 클래스의 내부만 바뀐다. 시그니처가 유지되므로 호출부 약 30개 파일은 수정이 없다.

클래스 다이어그램
클래스 다이어그램

7. 변경 파일 목록

파일 변경 Owner
build.gradle 좌표 변환 라이브러리 의존성 추가 -
src/main/java/com/msg/fillmap/grid/GridConstants.java STEP 상수 제거, 좌표계 정의와 셀 크기로 교체 A
src/main/java/com/msg/fillmap/grid/GridEncoder.java 내부 계산 교체(시그니처 유지) A
src/main/resources/db/migration/V28__*.sql 신규 마이그레이션: grids 재구축(중심점, 경계 도형, region_code 재판정 포함), videos.grid_id 재계산, user_grids 재구성, sponsor_ads와 mission_grids 재계산 -
src/main/resources/seed/zones.json zone 사각형 재산출(확정 시드 48건을 위경도로 역산해 새 인덱스로 재양자화) A
src/test/resources/fixtures/zone-naming.json 갱신 불요 확인(합성 인덱스 기반이라 좌표계 전환과 무관, 2026-08-08 실측 정정) A
src/test/resources/fixtures/ (신규) 프론트 정합성 샘플 200건 픽스처와 생성 검증 테스트 A
격자 ID를 하드코딩한 테스트 전반 기대값 갱신 A/B
.claude/rules/glossary.md, .claude/docs/grid-system.md 격자 계산 규칙 항목 개정 -

참고: 축제와 코스 미션의 시드 원천 JSONL은 레포 밖(fillmap-data 파이프라인)에 있어 이 목록에 없다. DB에 이미 적재된 행은 마이그레이션이 재계산하지만, 원천 파일의 gridId 재산출은 미션 기능을 다시 쓰는 시점으로 미루기로 확정했다(8절). 그 전에 시드를 다시 돌리면 구 체계 값이 들어오므로 시딩 플래그는 꺼 둔 상태를 유지한다.

8. 미해결 질문

남은 질문 없음. 초안의 4건은 2026-08-08 다음과 같이 확정했다.

  • 뱃지 소급: 회수하지 않는다. 정식 배포 전이라 소급 조정이 필요 없다(FR-12로 반영).
  • zone 재작도: 확정 시드 48건의 사각형을 위경도로 역산해 새 격자 인덱스로 다시 양자화한다. 처음엔 원천 데이터 파이프라인 재실행으로 정했으나, 작도 산출물에 위경도 원본이 보존돼 있음을 확인하고 검수 반복이 없는 역산 방식으로 같은 날 재확정했다.
  • 미션 시드 원천 JSONL: 지금은 재산출하지 않고, 미션 기능을 다시 쓰는 시점에 한다.
  • FE 티켓: 발행하지 않는다. 프론트 작업 시점에 백엔드 레포(계약과 픽스처)를 입력으로 진행한다.
  • 좌표 범위(2026-08-08 추가 확정): Codex 교차 리뷰가 EPSG:5179의 한국 한정 성격과 무제한 좌표 계약의 충돌(원거리 좌표에서 변환 발산, 비정상 격자 ID)을 지적했다. 비목표였던 "범위 제한 신설 안 함"을 뒤집어 한국 범위 검증을 FR-13으로 세웠다. 이후 실측에서 같은 범위의 검증이 MSG-93에서 이미 구현돼 있음이 확인돼(KoreaCoordinates, 에러코드 3400), FR-13은 신규 구현이 아니라 기존 구현 확인과 데이터 이행 사전 검사로 충족된다.

[^1]: EPSG:5179. 한국 전역을 하나의 원점으로 미터 단위로 다루는 국가 표준 평면 좌표계. 좌표가 미터라서 "100으로 나누면 100m 격자"가 정확히 성립한다. 네이버 지도가 내부적으로 쓰는 좌표계이기도 하다. [^2]: 반열림 구간. 하한은 포함하고 상한은 제외하는 구간 [a, b). 셀 경계선 위의 점이 두 셀에 동시에 속하는 일을 막는다. [^3]: region_code. 격자 중심점이 속한 행정동 코드. 격자를 "성수동1가"처럼 부르는 폴백 표시와 탐험률 집계에 쓰인다. [^4]: 좌표계 정의 문자열. 투영법, 원점, 타원체 같은 좌표계의 수학적 정의를 한 줄로 적은 것(proj4 형식). 서버와 프론트가 이 문자열이 글자 단위로 같아야 같은 계산 결과가 나온다. [^5]: Flyway. DB 스키마와 데이터 변경을 번호 붙은 SQL 파일로 관리해 순서대로 자동 적용하는 도구.