미션 지도 탐색 개편¶
티켓: 미발행 (관련 MSG-368, 마커 묶음은 MSG-437) · 작성일: 2026-08-13 · 작성: prd-writer 상태: 검토됨 (2026-08-13 성민 승인, 2026-08-20 마커 묶음 개정은 팀원 K 확정, 2026-08-24·2026-08-25 비로그인 개방 확정은 사용자 승인)
1. 문제 상황¶
지도 홈 상단에는 칩이 넷 있다. 핫구역, 지역축제, 팝업스토어, 경로추천이다. 뒤의 셋이 미션인데 화면 구조를 핫구역에서 그대로 물려받았다. 칩을 누르면 좌측 패널에 격자별 영상 목록이 뜬다.
핫구역에서는 이 구조가 맞다. 핫구역의 정의 자체가 "업로드 신호가 많은 격자"라서 격자를 나열하면 곧 영상이 나열된다. 미션은 반대다. 미션 격자는 축제 반경이나 코스 경로를 격자로 양자화한 것이라 영상 유무와 상관이 없다. dev 실측으로 미션이 덮은 격자 21,529칸 중 영상이 하나라도 있는 칸은 24칸, 0.1%다. 지역축제 칩을 누르면 좌측 패널이 사실상 항상 비어 있다.
더 큰 문제는 미션이 화면에 없다는 것이다. 축제 이름도, 언제까지인지도, 내가 얼마나 했는지도 나오지 않는다. 지도에는 보라색이나 주황색으로 칠해진 영역만 있어서 그게 무슨 축제인지 알 수 없다. 데이터베이스에는 제목과 기간과 목표 칸 수가 다 있고 API도 그걸 내려주는데 화면이 쓰지 않는다. 사용자 입장에서는 이게 미션인 줄 알 방법이 없다.
쓸 수 있는데 안 쓰는 데이터가 더 있다. 수집 원본에는 축제 설명과 장소, 팝업 운영시간과 주소, 코스 거리와 소요시간과 난이도가 들어 있다. 적재하지 않은 이유는 MSG-224와 MSG-235가 "missions에 컬럼이 없어 미적재, 신규 DDL 금지"로 정했기 때문이다. 그때는 미션이 판정용이라 이름과 좌표면 충분했다. 보여주는 화면이 생기면서 사정이 달라졌다.
2. 목적 · 목표¶
목적: 미션을 "영상이 걸린 격자 묶음"이 아니라 "가야 할 곳"으로 읽히게 한다. 영상이 아직 한 편도 없는 미션이 대다수인 초기 상태에서도 화면이 성립해야 한다.
목표
- 칩을 누르면 지금 지도에 보이는 범위의 미션 목록이 나오고, 영상이 0개여도 카드가 제 역할을 한다
- 카드 한 장에서 무슨 미션인지, 언제까지인지, 어디인지, 내가 얼마나 했는지를 안다
- 미션을 선택하면 상세에서 그 미션의 정보와 다른 사람이 올린 영상을 본다
- 지도에서 어느 영역이 어느 미션인지 이름으로 구분한다
비목표
- 미션을 만들거나 고치는 운영 도구는 만들지 않는다
- 스탬프북 조회 화면은 이번 범위가 아니다 (FR-MISSION-12, MSG-219)
- 앱 바텀시트 레이아웃은 다루지 않는다. 이번 시안은 웹 1440 기준이다
- AREA, THEME, CONTINUOUS 유형은 대응 칩이 없어 노출하지 않는다. 현재 적재 데이터도 0건이다
3. 기능 요구사항¶
| ID | 요구사항 | 우선순위 |
|---|---|---|
| FR-1 | 사용자가 미션 칩을 선택하면 지금 지도에 보이는 범위 안의 해당 종류 미션 목록을 본다 (SRS FR-MISSION-13 상세화) | Must |
| FR-2 | 미션 카드는 제목, 남은 기간 또는 상시 여부, 위치, 목표 대비 내 진행도를 담는다 | Must |
| FR-3 | 그 미션 영역에 영상이 하나도 없어도 카드는 온전히 표시된다. 영상 수는 있을 때만 덧붙는다 | Must |
| FR-4 | 보이는 범위에 미션이 없으면 오류가 아니라 빈 목록과 안내 문구를 보여준다 | Must |
| FR-5 | 카드를 선택하면 미션 상세를 열고, 선택된 카드는 목록에서 구분 표시된다 | Must |
| FR-6 | 상세는 미션 영역을 지도 미리보기로 보여준다. 축제와 팝업은 경계 사각형, 코스는 경로와 스팟 순서를 표시한다 | Must |
| FR-7 | 코스 상세는 포토스팟 목록을 순서대로 보여주고 스팟마다 방문 여부를 구분한다 | Must |
| FR-8 | 상세 하단에서 그 미션 영역에 올라온 영상을 최신순으로 본다. 영상이 없으면 첫 영상을 유도하는 빈 상태를 보여준다 | Must |
| FR-9 | 영상 카드에는 작성자 닉네임을 표시한다 (glossary 2026-08-04 확정) | Must |
| FR-10 | 지도에 표시된 미션 영역마다 미션 이름표가 함께 보이고, 그 이름은 목록 카드의 제목과 같다 | Must |
| FR-11 | 미션 상세에서 미션 설명과 장소를 본다. 원본 출처가 있으면 출처를 함께 표기한다 | Must |
| FR-12 | 코스 미션은 거리, 소요시간, 난이도를 본다 | Must |
| FR-13 | 팝업 미션은 운영시간과 주소를 본다 | Must |
| FR-14 | 미션에 대표 이미지가 있으면 목록 카드와 상세에 표시하고, 없으면 빈 자리로 두되 레이아웃이 무너지지 않는다 | Must |
| FR-15 | 이름이 없는 코스 경유 지점은 격자 표시명으로 대신 보여주고, 정식 명소가 아님을 구분한다 | Should |
| FR-16 | 완료한 미션은 목록과 상세에서 완료 상태로 구분된다. 완료 이후에도 목록에서 사라지지 않는다 | Must |
| FR-17 | 기간이 끝난 미션은 목록에 나오지 않는다. 다만 이미 받은 스탬프는 영향받지 않는다 (FR-MISSION-04) | Must |
| FR-18 | 세 종류 모두 상세의 주 행동은 촬영 유도로 같다. 길찾기처럼 앱 밖으로 나가는 행동은 두지 않는다 (SRS FR-MISSION-13) | Must |
| FR-19 | 축제 소스 전환 후에도 이미 발급된 스탬프가 가리키는 미션이 사라지지 않는다 | Must |
| FR-20 | 축제와 팝업은 지도에 점 마커로 표시한다. 판정 범위를 상시 도형으로 그리지 않는다 | Must |
| FR-21 | 축소 화면에서 팝업과 축제 마커는 행정 단위(동, 구, 시)로 묶여 지역 이름과 개수로 표시된다. 축척별 단위 전환은 지도 홈 도감 집계와 같다(대략 동 250m~500m, 구 1km~8km, 시 16km부터 전체). 코스는 1km 거리 기준으로 화면이 묶는다 (2026-08-20 개정, §8 참조. 기존 "팝업·축제 500m 거리 기준 화면 묶음" 대체). 묶인 것을 선택하면 목록이 그 미션들로 좁혀진다 | Must |
| FR-22 | 미션을 선택했을 때만 그 미션의 판정 범위를 지도에 표시한다 | Must |
| ~~FR-23~~ | ~~칩마다 지금 보이는 범위의 미션 개수를 배지로 보여주고, 0이면 칩을 숨긴다~~ 폐기 (2026-08-14, §8 참조). 칩은 종류를 고르는 역할만 하고 항상 표시된다 | 폐기 |
| FR-24 | 목록 카드와 지도 마커는 서로 연동된다. 카드를 가리키면 해당 마커가 강조되고, 마커를 선택하면 목록이 그 카드로 이동한다 | Should |
| FR-25 | 코스는 가까운 줌에서 경로선으로 표시한다. 일정 축척보다 넓게 보면 대표 마커로 바꿔 1km 기준으로 묶고, 확대하면 다시 경로선으로 돌아온다 (2026-08-15 개정, §8 참조) | Must |
| FR-26 | 팝업의 판정 범위는 좌표에서 반경 40m 안에 걸치는 격자다. 팝업 위치에 따라 1칸에서 4칸 사이가 된다 | Must |
| FR-27 | 상세의 미리보기는 실제 지도 위에 미션 영역이나 경로를 얹어 보여준다. 주변 지형과 지명이 함께 보여야 위치를 가늠할 수 있다 | Must |
| FR-28 | 미리보기 배율은 미션 종류와 크기에 맞춘다. 코스는 경로 전체가 들어오고, 팝업은 범위가 화면을 채울 만큼 당겨진다 | Should |
| FR-29 | 시 단위 축척처럼 개별 목록 조회가 성립하지 않는 넓은 화면에서도 팝업과 축제의 행정 단위 묶음은 조회된다. 묶음 마커는 도감 집계 버블과 같은 패턴이고 색으로만 구분한다 (2026-08-20 신설, §8 참조, SRS FR-MISSION-20) | Must |
엣지 케이스
- 미션 영역이 화면 경계에 걸쳐 일부만 보이는 경우에도 목록에 포함한다. 코스는 하나가 수 킬로미터에 걸쳐 있어 경로 중간만 걸린 코스가 통째로 사라지면 안 된다 (MSG-368 완료 조건)
- 대표 이미지 원본이 사라졌거나 형식을 읽을 수 없으면 이미지 없는 미션과 같게 처리한다
- ~~비로그인 상태에서는 진행도 없이 미션 정보만 보여준다~~ 폐기 (2026-08-14, §8 참조). 미션 목록도 로그인이 있어야 볼 수 있다
- 지도를 광역시 하나보다 넓게 줄여 보면 목록을 채우지 않고 확대를 권하는 안내를 보여준다. "이 범위에 미션이 없어요"(FR-4)와 다른 상태다. 하나는 여기엔 없다는 뜻이고 하나는 너무 넓어서 아직 안 보여준다는 뜻이라 사용자가 할 행동도 이동과 확대로 갈린다 (2026-08-14 확정, §8 참조)
4. 비기능 요구사항¶
| 분류 | 요구사항 |
|---|---|
| 성능 | 칩 전환과 지도 이동에서 목록이 300ms 안에 갱신된다. 활성 미션은 축제 61건, 코스 148건, 팝업 195건으로 합계 404건이다 (2026-08-14 재측정. 작성 시점 값이던 축제 385·코스 148·팝업 157은 TourAPI 전환과 기간 만료를 반영하지 않은 값이라 폐기한다. 적재 건수는 축제 222·팝업 227·코스 148로 합계 715건이고 활성과 구분해 읽는다) |
| 성능 | 미션 목록 응답은 지금 지도에 보이는 범위로 제한된다. 전국 전량을 내려보내지 않는다 |
| 성능 | 밀집 지역에서도 지도가 읽혀야 한다. 한 뷰포트(0.5도)에 팝업 미션이 169개까지 걸린다 (2026-08-14 재측정, 강남역 중심. 작성 시점 값이던 38개는 더 좁은 범위 기준이라 폐기한다) |
| 데이터 정합 | 팝업 판정 범위 축소는 이미 발급된 스탬프를 회수하지 않는다 (FR-MISSION-04) |
| 데이터 정합 | 외부에서 받아 온 이미지는 원본이 사라져도 화면이 깨지지 않아야 한다. 팝업은 기간이 끝나면 원본 페이지가 없어진다 |
| 데이터 정합 | 진행도는 조회 시점의 실제 업로드 기록과 일치한다. 별도 집계 값을 두고 어긋나게 하지 않는다 |
| 운영 | 미션 메타데이터 추가는 기존 미션 데이터를 지우지 않고 채우는 방향이어야 한다. 이미 발급된 스탬프에 영향이 없어야 한다 |
| 운영 | 외부 데이터 재수집은 하루 안에 1회로 끝나야 한다. TourAPI 일 쿼터가 1,000콜이다 |
5. 시퀀스 다이어그램¶
미션 목록을 열고 상세로 들어가는 흐름이다. 진행도가 사용자마다 다르다는 점이 지금 구조와 가장 크게 부딪히는 지점이었고, 그 충돌은 아래 §8에서 "목록은 전역으로 두고 진행도는 따로 받아 화면에서 합친다"로 정리했다. 아래 그림은 그 결론을 반영한 것이다. 목록과 상세가 서로 다르게 그려지는 것이 핵심이다. 목록은 모든 사용자에게 같아야 캐시가 성립하지만, 상세는 미션 하나를 여는 사용자별 조회라 진행도를 담아도 된다.
6. 클래스 다이어그램¶
변경되는 타입만 적는다. 상세 조회와 영역 영상 조회가 새로 생기고, 미션 목록 응답에 메타데이터가 붙는다. 진행도는 상세에만 붙는다.
목록 조회는 사용자를 받지 않는다. 화면의 "내 진행 0/1칸"과 완료 배지는 목록 응답이 아니라
클라이언트가 합성한다. 재료는 내 진행도 조회(GET /api/missions/progress, MSG-398)와 내
스탬프다. 진행도의 기준은 미션 기간 안에 촬영한 내 영상이 있는 격자 수다(2026-08-15 확정, §8 —
당초의 "수집 격자와의 교집합" 정의를 대체). 이렇게 두는 이유는 목록에 전역 캐시가
걸려 있어서다. 사용자별 값이 하나라도 섞이면 캐시를 사람 수만큼 떠야 한다(SRS FR-MISSION-02).
상세 조회는 사용자를 받으므로 진행도를 담는다. 상세는 미션 하나를 여는 조회라 캐시 전제가 없고, 코스 스팟별 방문 여부를 이미 사용자별로 계산한다. 코스의 "내 진행 2/3곳"은 그 방문 여부의 합계라 같은 계산에서 나온다. 축제와 팝업의 "0/1칸"도 같은 기준(기간 내 촬영 영상이 있는 미션 격자 수)이다.
7. 변경 파일 목록¶
| 파일 | 변경 | Owner |
|---|---|---|
src/main/java/com/msg/fillmap/mission/controller/MissionController.java |
수정: 뷰포트 목록·상세 엔드포인트 추가 | B |
src/main/java/com/msg/fillmap/mission/service/MissionQueryService.java |
수정: 조회 메서드 2종 추가 | B |
src/main/java/com/msg/fillmap/mission/service/impl/MissionQueryServiceImpl.java |
수정: 뷰포트 필터·진행도 결합·캐시 키 재설계 | B |
src/main/java/com/msg/fillmap/mission/dto/MissionResponseDto.java |
수정: 메타데이터·진행도 필드 추가 | B |
src/main/java/com/msg/fillmap/mission/dto/MissionDetailResponseDto.java |
신규 | B |
src/main/java/com/msg/fillmap/mission/entity/Mission.java |
수정: 신규 컬럼 매핑 | B |
src/main/java/com/msg/fillmap/mission/repository/MissionRepository.java |
수정: 뷰포트 조회·상세 조회 | B |
src/main/java/com/msg/fillmap/mission/repository/MissionGridRepository.java |
수정: 스팟별 방문 여부·영상 수 집계 | B |
src/main/java/com/msg/fillmap/mission/seed/FestivalRecord.java |
수정: TourAPI 필드로 교체(설명·장소·이미지) | B |
src/main/java/com/msg/fillmap/mission/seed/FestivalJsonlReader.java |
수정: TourAPI 응답 형식 수용 | B |
src/main/java/com/msg/fillmap/mission/seed/FestivalMissionSeeder.java |
수정: 소스 전환·기존 스탬프 보존 이행 | B |
src/main/java/com/msg/fillmap/mission/seed/PopupRecord.java |
수정: 운영시간·주소·원문 링크 수용 | B |
src/main/java/com/msg/fillmap/mission/seed/PopupMissionSeeder.java |
수정: 신규 컬럼 적재, 판정 격자를 9×9 고정에서 반경 40m 산출로 교체 | B |
src/main/java/com/msg/fillmap/mission/seed/CourseRecord.java |
수정: 거리·소요시간·난이도·설명 수용 | B |
src/main/java/com/msg/fillmap/mission/seed/CourseMissionSeeder.java |
수정: 신규 컬럼 적재 | B |
src/main/java/com/msg/fillmap/video/repository/VideoRepository.java |
수정: 미션 격자 영역 영상 목록 조회 | B |
src/main/resources/db/migration/V31__mission_metadata.sql |
신규: missions 메타데이터 컬럼 추가 | - |
2026-08-20 추가분 (MSG-437 행정 단위 묶음, 세부는 스펙에서 확정):
| 파일 | 변경 | Owner |
|---|---|---|
src/main/java/com/msg/fillmap/mission/controller/MissionController.java |
수정: 행정 단위 집계 엔드포인트 추가 | B |
src/main/java/com/msg/fillmap/mission/service/** |
수정: 스냅숏 기반 단위별 집계 | B |
src/main/java/com/msg/fillmap/mission/dto/** |
신규: 집계 응답 DTO | B |
src/main/resources/db/migration/V{n}__mission_region_code.sql |
신규: missions 행정 귀속 컬럼 (귀속 방식은 스펙 몫) | - |
| 팝업·축제 시더 | 수정: 적재 시 행정 귀속 판정 (택하는 경우) | B |
Owner는 전부 B다. com.msg.fillmap.mission과 com.msg.fillmap.video가 콘텐츠 도메인이다.
격자 표시명이 필요한 부분은 ZoneNameQueryService를 통해 받는다. Owner A 코드는 고치지 않는다.
예외 하나(2026-08-15 확정): 팝업 판정 반경 산출은 좌표를 격자로 옮기는 계산이라 그 단일 진실
원천인 grid/GridEncoder에 정책 없는 기하 메서드 하나를 순수 추가한다. 변환 산술을 시더로
복제하지 않기 위해서이고, 반경 값과 정책은 여전히 Owner B(시더)가 가진다.
8. 확정 사항 (2026-08-13 성민, 2026-08-14 · 2026-08-15 · 2026-08-20 · 2026-08-22 · 2026-08-24 · 2026-08-25 · 2026-08-26 추가)¶
2026-08-26 추가 (사용자 확정, MSG-491)¶
- 비로그인 개방을 화면이 실제로 부르는 마지막 세 조회까지 넓혀 끝낸다. 위 2026-08-22(MSG-454)와 2026-08-24(MSG-467)와 2026-08-25(MSG-469) 확정의 잔여분이고, 이번으로 비로그인 지도의 조회 개방은 닫힌다. 발단은 2026-08-26 재실측이다. 앞선 셋이 배포된 뒤에도 익명 401이 남은 경로를 전수로 확인했더니, 사용자별 값이라 막혀야 마땅한 것을 빼고 셋이 남았다.
GET /api/regions/{regionCode}/grids(동 격자 카드 목록, FR-VIDEO-17): 지도 홈 좌측 패널이 그 동의 공개 영상 카드를 그리는 재료다. 사용자 정보를 받지 않는 조회라 응답이 로그인 때와 같다.GET /api/videos/{videoId}(영상 재생, FR-VIDEO-12·13·16): 아래 공개범위 규칙이 붙는다.GET /api/regions/explore(전체 지역 목록, FR-SEARCH-15): 아래 정렬 단서가 붙는다.- 비로그인 재생은 전체 공개 영상만 된다. 나만 보기와 친구 공개를 요청하면 지금 로그인 사용자가 남의 비공개 영상을 요청할 때와 같은 자리에서 같은 응답으로 거절된다. 응답이 글자 단위로 같아야 하는 이유는 2026-08-03 친구 공개 확정이 세운 것과 같다. 거절 응답이 갈리면 그 차이가 영상의 존재와 공개범위를 알려주는 신호가 된다. 새 에러코드를 만들지 않는 것도 그래서다.
- 익명 재생도 조회수에 센다. 지금 규칙이 "재생 URL을 실제로 발급했고 소유자가 아닐 때"인데, 익명은 언제나 소유자가 아니므로 규칙을 그대로 적용하면 세는 쪽이 된다. 비로그인 재생을 빼면 지도를 비로그인에 열어 둔 상태에서 인기순 정렬의 표본이 로그인 쪽으로 기운다. 2026-08-25에 비로그인 검색을 인기 검색어 집계에 넣기로 한 것과 같은 판단이다. 익명이 조회수를 부풀릴 수 있다는 점은 그때와 마찬가지로 수용하고, 배포 뒤 값이 부자연스럽게 흔들리면 그때 호출량 제한을 요구사항으로 올린다.
- 비로그인 전체 지역 목록은 개인화 없이 격자 수 내림차순만 남는다. 지금 정렬은 내가 최근 업로드한 지역을 앞에 두고 나머지를 격자 수 내림차순으로 잇는데, 비로그인은 앞 절의 재료가 없다. 익명에게는 그 절이 통째로 비어 뒤 절만 남는 형태이고, 페이지 계약(20개와 커서)은 그대로다. 응답 필드도 그대로이며 개인화에 쓰이던 값만 비어 나간다.
- 속도는 달라지지 않는다. 이 조회의 비용은 대부분 전국 격자를 세는 집계에 있고 그것은 사용자와 무관하다(MSG-461 실측 266ms 가운데 257ms). 개인화는 내 점령 격자를 찾는 부분 하나뿐이라 익명이면 찾을 행이 없어 끝난다. 넓은 축척 조회를 새로 만들 필요가 없다.
- 단일 격자 조회(FR-GRID-06)는 이번에도 열지 않는다. 2026-08-25 확정을 그대로 유지한다. 화면이 필요한 이름 재료는 진입점 응답이 이미 싣고 있어(핫구역과 경로 응답이 gridId와 zoneName과 zoneCell과 regionName을 함께 준다) 비로그인 격자 상세는 그 값에 대표 영상과 공개 영상 목록을 붙이면 완성된다.
- 개방은 GET 한정이고 경로를 하나씩 열거한다. 업로드를 비롯한 쓰기와 도감·프로필·수집률 같은
사용자별 조회는 로그인 필수를 그대로 유지한다. 스크래핑 우려의 정리는 2026-08-22 확정의 수용
논리를 따른다. 구현 정본은
docs/spec/MSG-491.md
2026-08-25 추가 (사용자 확정, MSG-469)¶
- 비로그인 개방을 지도 홈이 실제로 부르는 나머지 조회까지 넓힌다. 아래 2026-08-22(MSG-454)와 2026-08-24(MSG-467) 확정의 마지막 잔여분이다. 앞의 둘이 칩과 행정동을 열었는데도 비로그인 지도가 반쪽인 상태가 남았다. QA 결함 리스트 대조 중 로컬 실측으로 확인한 것은 둘이다. 앱 기동 때 부르는 구역 목록이 막혀 검색바의 구역 이동이 되지 않고, 격자를 눌러도 대표 이미지와 영상이 나오지 않는다. 열 대상은 여섯이고 모두 응답이 로그인 때와 같다.
GET /api/zones(구역 목록, FR-ZONE-11): 검색바에서 "서면"처럼 구역 이름을 쳐서 그 자리로 지도를 옮기는 기능의 재료다. 이 목록이 없으면 비로그인 검색바가 장소 검색만 되고 구역 이동이 빠진다.GET /api/search/trending(인기 검색어, FR-SEARCH-07)- 격자 대표 영상과 격자의 공개 영상 목록(전역 노출 경로, FR-VIDEO-17·18): 축제와 팝업에서 격자를 눌렀을 때 커버와 영상 목록을 그리는 재료다.
GET /api/search/places(장소 검색, FR-SEARCH-01): 아래 집계 단서가 붙는다.- 격자의 시간대별 업로드 분포(FR-MAP-09): 칩을 켠 채로 격자를 눌렀을 때 나오는 차트의 재료다. 노출 자리가 이번에 여는 대표 이미지·영상 목록과 같으므로 함께 열지 않으면 그 자리만 비어 보인다. 세는 대상이 전역 공개 영상 전부라 내 값이 아니다.
- 단일 격자 조회(FR-GRID-06)는 열지 않는다. 응답에서 사용자와 무관한 값은 이름 재료뿐인데 그것은 이미 다른 경로로 얻는다. 구역 이름과 칸 번호는 위에서 여는 구역 목록으로 화면이 만들고, 행정동 이름은 2026-08-24에 연 역지오코딩이 준다. 남는 두 값인 점령 여부와 영상 수는 둘 다 내 것이라(영상 수도 전체가 아니라 내가 그 격자에 올린 개수다) 비로그인에게는 채우지 않음과 0으로 고정된 껍데기만 나간다. 열어도 얻는 것이 없으므로 로그인 필수를 유지하고, FR-GRID-06의 문안도 손대지 않는다.
- AI 경로 추천은 로그인 필수를 유지한다(2026-08-25 사용자 확정). 호출마다 AI 모델을 부르는 유료 경로라 익명에 열면 비용이 그대로 노출된다. 상단 칩의 "경로추천"과 헷갈리지 않아야 한다. 칩은 산책 코스 미션이고 미션 목록 조회로 그려지므로 2026-08-22 확정으로 이미 비로그인에서 동작한다. 이번에 로그인으로 남기는 것은 자연어로 방문 순서를 짜 주는 별개 기능이다.
- 장소 검색도 비로그인에서 된다(2026-08-25 사용자 확정). 검색바는 로그인 여부와 무관하게 지도를 움직이는 기본 도구라 이것만 막히면 비로그인 지도가 다시 반쪽이 된다. 결과는 로그인 때와 같다.
- 비로그인 검색도 인기 검색어 집계에 넣는다(2026-08-25 사용자 확정). 지도를 비로그인에 열어 두는 이상 검색의 다수가 비로그인에서 나올 텐데, 로그인 사용자만 세면 순위가 소수 표본이 된다. 무엇이 인기인지를 보여주는 자리라 표본이 한쪽으로 기우는 것이 더 큰 문제다.
- 넣되 "한 사람이 하루에 같은 말을 여러 번 쳐도 한 번만 센다"는 규칙은 비로그인에도 그대로 적용한다. 지금 그 규칙은 로그인 사용자를 기준으로 걸려 있고, 기준이 없으면 아예 세지 않는 것이 기존 결정이다(MSG-258). 세지 않으면 표본이 빠지고 규칙 없이 세면 같은 말을 무한히 밀어 넣을 수 있으므로, 비로그인은 화면이 들고 있는 방문자 식별값을 기준으로 삼는다. 행사방 열람 인원이 같은 방식으로 익명을 세고 있어 새 개념이 아니다. 그 값이 없는 요청은 검색은 정상으로 되고 집계에만 안 잡힌다. 화면이 값을 붙이기 전까지도 검색이 막히지 않게 하기 위해서다.
- 이 방식이 조작을 완전히 막지는 못한다. 식별값을 매번 새로 만들면 같은 말을 다시 밀어 넣을 수 있는데, 로그인 쪽도 계정을 여럿 만들면 마찬가지라 정도의 차이다. 다만 값을 바꾸는 쪽이 훨씬 쉬우므로 배포 뒤 순위가 부자연스럽게 흔들리는지 지켜보고, 그런 일이 생기면 그때 호출량 제한을 요구사항으로 올린다.
- 바깥 지도 서비스 호출량은 이번에 제한을 걸지 않는다. 장소 검색은 외부 지도 서비스를 대신 불러 주는 경로라 로그인 없이 열면 호출이 늘 수 있다. 다만 2026-08-22 확정이 세운 방침대로 호출량 제한은 필요해질 때 별도 요구로 다루고, 대신 배포 뒤 호출량을 지켜보는 것을 조건으로 둔다. 급증이 확인되면 그때 제한을 요구사항으로 올린다.
- 개방은 GET 한정이고 경로를 하나씩 열거한다. 업로드를 비롯한 쓰기와 도감·프로필·수집률 같은
사용자별 조회는 로그인 필수를 그대로 유지한다. 스크래핑 우려의 정리는 2026-08-22 확정의 수용
논리를 따른다. 구현 정본은
docs/spec/MSG-469.md
2026-08-24 추가 (사용자 확정, MSG-467)¶
- 비로그인 개방을 화면이 쓰는 행정동 조회까지 넓힌다. 아래 2026-08-22 확정(MSG-454)이
핫구역 1종과 미션 조회 4종을 익명에 열었는데, 팝업스토어·지역축제 칩의 좌측 패널이 위치줄과
지역 필터를 그리는 데 쓰는 행정동 조회 2종은 범위에 없었다. 그래서 비로그인으로 칩을 열면
미션 목록은 나오는데 행정동 이름 로드만 401로 실패한다. 좌표 역지오코딩(FR-REGION-02,
GET /api/regions/reverse-geocode)과 시군구 목록(FR-REGION-15,GET /api/regions/districts)을 익명 허용으로 맞춘다. 둘 다 사용자 무관 값이라 응답 계약은 그대로다. - 내 수집률을 주는 stats 계열 조회 4종은 사용자별 값이라 로그인 필수를 유지한다. 개방은 GET
한정이며, 스크래핑 우려의 정리는 2026-08-22 확정의 수용 논리를 그대로 따른다. 이번에 열리는
값은 행정동 이름과 시군구 목록으로, 공공 행정 데이터라 미션 정보보다도 민감성이 낮다.
구현 정본은
docs/spec/MSG-467.md
2026-08-22 추가 (팀원 K 확정, MSG-454)¶
- 비로그인 미션 열람을 다시 허용한다. 아래 2026-08-14 확정("비로그인 미션 열람을 폐기한다")의 번복이다. 행사방 작업(MSG-439, 2026-08-21)에서 "상단 칩은 비로그인, 업로드는 로그인" 원칙이 확정되면서 행사 칩만 익명에 열렸고, 같은 상단 칩 라인의 핫구역·지역축제·팝업스토어·경로추천이 로그인 벽 뒤에 남아 비대칭이 생겼다. 기획의도는 상단 칩 전체가 로그인 없이 보이는 것이라 미션 조회(목록·집계·상세·영상 목록)와 핫구역 조회를 익명 허용으로 맞춘다. 사용자별 값인 진행도 조회는 로그인 유지이고, 미션 상세의 개인화 필드(진행도·스탬프)는 익명이면 null 값이다.
- 08-14 폐기의 근거였던 "인증 없는 도메인 데이터 창구(스크래핑)" 우려는 완화가 아니라
수용으로 정리한다. 뷰포트 상한이 있어도 전량 수집은 가능하다. 집계 조회가 시도 단위에서
한 변 10도 bbox를 허용해 축제·팝업 미션 id 전체를 한두 번 호출로 얻을 수 있고, 이번에
공개되는 상세·영상 목록으로 그 id들을 열거할 수 있으며, 코스도 0.5도 타일링이면 수집된다.
그래도 여는 이유는 08-14 원문의 판단("나가는 값이 공공 출처 축제와 팝업과 코스 정보라
민감하지는 않다")이 그대로이고, 행사 조회가 이미 같은 성격의 데이터를 익명으로 열어 선례가
됐으며, 상단 칩이 로그인 없이 보여야 한다는 제품 요구가 이 비민감 데이터의 수집 가능성보다
크다고 보았기 때문이다. 호출량 제한(rate limit)은 이번에도 도입하지 않으며 필요해지면 별도
요구로 다룬다. 구현 정본은
docs/spec/MSG-454.md
2026-08-20 추가 (팀원 K 확정, MSG-437)¶
- 팝업과 축제 마커의 묶음 기준을 거리(500m)에서 행정 단위 집계로 바꾼다. 아래 2026-08-15
확정("마커를 묶는 일은 화면이 한다")의 팝업·축제 부분을 번복한다. 지도를 축소하면 도감
격자가 동, 구, 시 마커로 갈아타는 것과 같은 기준(축척별 전환 포함)으로 미션 핀도 묶이고,
마커 생김새도 도감 집계 버블(디자인 "지도 클러스터링 — 행정 단위 집계" 프레임
14599:7025)과 거의 같게 하되 색만 미션용으로 구분한다. 거리 기준으로 묶으면 묶음에 이름이 없어 "핀 3개" 이상을 말하지 못하는데, 행정 단위로 묶으면 "해운대구 12"처럼 지역 이름이 붙어 축소 화면에서도 정보가 된다. 코스의 1km 화면 묶음과 줌별 표시(경로선과 대표 마커)는 그대로다 - 넓은 축척의 묶음 조회는 서버 몫이 된다. 개별 목록 조회는 뷰포트 한 변 0.5도 상한이
있어(아래 2026-08-14 확정) 시 단위 축척에서는 조회 자체가 성립하지 않는다. 도감이 같은
문제를 집계 조회(
/api/grids/aggregation, MSG-356)로 푼 것처럼 미션도 넓은 축척용 집계 조회를 신설한다. 2026-08-15 확정이 서버 묶음을 기각했던 근거 둘은 이렇게 정리된다. 줌을 서버가 받으면 전역 캐시가 쪼개진다는 우려는, 단위가 연속 줌이 아니라 동·구·시 세 값이고 응답이 여전히 사용자 무관이라 기존 1시간 전역 스냅숏에서 집계하는 것으로 해소된다. 묶음 선택 시 목록 좁힘에 개별 미션이 필요하다는 근거는 남은 설계 과제이고 (묶음에 미션 식별자를 실을지, 화면이 좁은 뷰포트로 재조회할지) 스펙에서 정한다 - "너무 넓어서 안 보여준다" 상태의 지도 면은 집계 마커가 채운다. 0.5도 초과 축척에서 기존에는 지도에 그릴 것이 없었는데 이제 행정 단위 마커가 뜬다. 좌측 목록(패널)이 그 축척에서 확대 안내를 유지할지, 묶음 단위 목록을 보여줄지는 화면 몫이라 디자인과 정한다
2026-08-15 추가¶
- 마커를 묶는 일은 화면이 한다. 기준은 팝업과 축제 500m, 코스 1km다. ~~(팝업·축제 부분은
2026-08-20 번복, 위 참조. 코스 1km는 유지)~~ 서버는 뷰포트 안
개별 미션만 내려주고 묶지 않는다(MSG-398). 이유는 둘이다. 묶인 것을 선택하면 목록이 그
미션들로 좁혀져야 해서(FR-21) 화면이 어차피 개별 미션을 전부 들고 있어야 하고, 확대하면
묶음이 풀려야 하는데 그 기준(줌)을 서버가 받는 순간 모든 사용자에게 같은 응답이라는 목록
캐시 전제가 줌별로 쪼개진다. 뷰포트 상한 덕에 화면이 받는 양은 실측 최대 169건이라 화면 쪽
부담도 없다. 구현은 네이버 지도 API의 클러스터링을 쓴다. 지도의 다른 레이어와 분담은 이렇다.
도감 격자는 넓은 줌에서 기존 서버 집계(
/api/grids/aggregation)로 갈아타고, 핫구역은 묶지 않고 뷰포트의 셀을 그대로 그린다 - 넓은 줌에서는 코스도 마커로 표시한다(FR-25 개정). 일정 축척보다 넓게 보면 경로선 대신 대표 마커로 바꾸고 1km 기준으로 묶으며, 확대하면 경로선으로 돌아온다. 경로선과 마커가 갈리는 축척 값은 FE가 정한다. 기존 "코스는 마커로 묶지 않는다"(2026-08-13 확정)는 가까운 줌에 한정된 서술이 된다
- 코스 마커의 호버와 클릭은 기존 확정의 조합으로 동작한다. 마커의 대표점은 1번 포토스팟 위치다. 호버하면 이름표(FR-10)와 함께 그 코스의 경로선을 미리보기로 그리고, 클릭하면 카드를 클릭한 것과 같다. 상세가 열리고(FR-24) 지도가 경로 전체가 들어오게 맞춰지며(FR-28), 줌이 내려가면서 마커가 경로선과 번호 스팟으로 돌아온다. 여러 코스가 묶인 마커를 클릭하면 목록이 그 코스들로 좁혀진다(FR-21). MSG-395가 만든 호버 강조 구조(선택·호버한 미션에만 이름표)를 그대로 확장한다
- 소스 스냅샷에서 사라졌는데 아직 끝나지 않은 팝업은 기존 판정 블록의 중심 근사 셀 1칸으로 좁힌다. 팝가에서 내려간 팝업은 원좌표가 스냅샷에만 있어 반경 40m 판정(FR-26)을 정확히 재현할 수 없다. 레거시 900m 블록을 그대로 두는 안과 미션을 종료 처리하는 안 대신, 참 판정 대비 최대 1칸 오차를 수용하는 중심 근사 1칸을 택했다(2026-08-15 확정, 근사 규칙과 구현은 MSG-385 스펙 D4)
2026-08-14 추가¶
- 칩 개수 배지를 폐기한다(FR-23). 지도를 움직일 때마다 종류 넷의 개수를 다시 세어 칩 상태를 맞춰야 하는데, 그 상태 관리 비용이 배지가 주는 값어치보다 크다고 보았다. 개수가 없어지므로 "0이면 칩을 숨긴다"도 함께 사라지고 칩은 항상 표시된다. 서버 쪽에서는 목록 조회가 종류별 개수를 따로 셀 필요가 없어진다
- 비로그인 미션 열람을 폐기한다(3절 엣지 케이스). 미션 목록도 로그인이 있어야 본다. 원래는
진행도만 빼고 미션 정보는 보여주려 했는데, 지금 서버는
SecurityConfig가 모든 경로에 인증을 걸고 있어서 그 화면이 성립하지 않는다. 열려면/api/missions/active를 permitAll로 빼야 하고, 그러면 인증 없이 부를 수 있는 도메인 데이터 엔드포인트가 처음 생긴다. 지금 인증 없이 열려 있는 것은 로그인과 토큰 재발급, 관측 지표, API 문서뿐이다. 나가는 값이 공공 출처 축제와 팝업과 코스 정보라 민감하지는 않지만, 호출 제한이 없어 통째로 긁어가기 좋은 창구가 하나 생기는 것이 얻는 것보다 크다고 보았다. 로그인 전 화면에서는 미션 목록을 부르지 않는다 (MSG-398 스펙 D10에서 발견된 PRD와 구현의 어긋남, 2026-08-14 성민 판단) - 목록 조회에 뷰포트 한 변 0.5도 상한을 두고, 그보다 넓으면 확대를 권하는 안내를 그린다. 실측이 이 값을 정했다. 지금 활성 미션은 404건이고 파라미터 없이 전량을 받으면 본문이 465KB다. 뷰포트를 10도로 열면 어느 중심에서든 그 404건이 그대로 나와서 상한 구실을 못 하고, 4도도 대전 중심에서 383건 429KB로 전량의 92%다. 국토가 남북 약 4도라 그 폭이면 이미 전국이기 때문이다. 0.5도는 한 변이 약 55km라 광역시 하나가 통째로 들어오고, 그 안의 최대가 강남역 192건 79KB다. 카드 192장이면 이미 스크롤이 한참이라 더 넓힐 이유가 화면 쪽에도 없다. 종류로 거르는 것만으로는 방어가 안 된다는 점도 확인했다. 코스만 골라도 10도에서 148건 356KB가 나가는데 코스 하나가 평균 2.4KB짜리 경로 좌표 배열이라 종류를 좁혀도 안 줄어든다. 넘었을 때 빈 목록을 내리지 않는 이유는 그렇게 하면 "여기엔 미션이 없다"와 "너무 넓어서 아직 안 보여준다"가 응답에서 구분되지 않아, 안내를 구현하지 않은 클라이언트가 "미션이 없어요"라는 거짓말을 그리기 때문이다. 화면이 두 상태를 가르는 재료는 자기가 방금 계산한 지도 폭이고, 그 값은 격자 레이어를 개별 조회에서 집계로 갈아타는 데 이미 쓰고 있어서 새 임계값을 만들지 않는다. 이 상태의 시안은 아직 없어 디자인에 요청한다 (2026-08-14 성민 판단, 근거 실측은 MSG-398 스펙 D6)
2026-08-13 확정¶
- 축제 소스를 TourAPI로 전환한다. TourAPI 축제는 461건이고 그중 456건(98%)이 이미지를 갖고 있다. 지금 소스인 공공데이터 표준데이터와는 이름 기준 38%만 매칭되고, TourAPI에만 있는 축제가 347건이다(강릉단오제 같은 큰 축제 포함). 전환하면 이미지가 98% 따라오고 건수도 385건에서 461건으로 는다. 다만 축제 미션의 자연키가 바뀌므로 이미 발급된 스탬프가 가리키는 미션이 사라지지 않게 하는 것이 전환의 전제 조건이다. 구체적 이행 방식은 스펙에서 정한다
- 진행도는 미션 목록과 분리해 조회한다. 미션 목록은 지금처럼 전역 캐시를 유지하고 내 스탬프와 방문 격자만 따로 받아 화면에서 합친다. 요청이 두 번 나가지만 캐시 전제를 지키는 쪽을 택했다. 이 규칙은 목록에만 걸린다. 상세는 미션 하나를 여는 사용자별 조회라 캐시 전제가 없고, 스팟별 방문 여부를 이미 사용자별로 내주므로 진행도를 함께 담는다(§6 참조). 목록 응답에 사용자별 값을 넣지 않는다는 요구는 SRS FR-MISSION-02이고, 진행도를 별개 조회로 받는다는 요구는 FR-MISSION-18이다
- 코스 상세의 주 버튼도 촬영 유도로 통일한다. 세 종류 모두 "이 미션 지역에서 촬영하기"다. 길찾기 버튼은 2026-08-10에 미도입으로 확정한 그대로 두었다 (SRS FR-MISSION-13)
- 팝업의 판정 범위를 좌표에서 반경 40m 안에 걸치는 격자로 좁힌다. 지금은 9×9, 반경 450m다. 팝업스토어는 가게 하나라 실제 위치가 점인데 옆 블록 카페에서 찍어도 스탬프가 나오므로 "그 팝업에 갔다"는 증명이 성립하지 않는다. 칸 수를 N×N으로 고정하지 않고 반경으로 정하는 것은 팝업이 격자 안 어디에 떨어지느냐가 제각각이기 때문이다. 가운데면 한 칸, 경계에 걸치면 두 칸, 모서리면 네 칸이 자연스럽게 나온다. 팝업 600곳 좌표 실측으로 2×2가 66%, 두 칸이 31%, 한 칸이 3%다. 반경을 30m 아래로 내리면 한 칸인 경우가 15%를 넘는데, 도심 위치 오차가 10~30m라 가게 안에 있어도 옆 칸으로 찍혀 인정이 안 되는 일이 생긴다. 축제는 행사장이 실제로 넓어 9×9를 유지한다. 이미 발급된 스탬프는 비회수 원칙대로 유지되고(FR-MISSION-04), 축소는 앞으로의 판정에만 적용된다
- 축제와 팝업을 지도에서 점 마커로 그린다. 밀집 지역 실측으로 뷰포트 하나(가로 1.5km)에 팝업 미션이 38개까지 걸린다. 900m 사각형 38장을 겹쳐 그리면 화면이 색으로 뭉개지고 이름표도 읽히지 않는다. 마커로 바꾸면 클러스터링이라는 표준 해법을 그대로 쓸 수 있다. 코스는 이미 표시(경로선)와 판정(스팟)이 분리돼 있어 그대로 둔다
- 코스 대표 이미지는 TourAPI 스팟 사진만 쓴다. 코스 위 포토스팟 중 사진이 있는 첫 스팟의 것을 쓴다. 결과가 사용자와 무관하게 항상 같아 미션 목록 캐시에 그대로 실을 수 있다. 사진이 있는 스팟이 하나도 없는 코스는 이미지 없이 표시한다. 사용자 영상 썸네일을 대표로 올리는 안은 값이 사람마다·시점마다 달라져 캐시를 깨므로 채택하지 않았다
- 외부 소개문은 원문 그대로 보여주고 화면에 출처를 표기하지 않는다. 축제 설명과 팝업 소개문을 줄이거나 다시 쓰지 않는다. 다만 TourAPI와 공공데이터 이용약관이 출처 표기를 조건으로 두는 경우가 있어 확인이 필요하고, 필요하면 화면 대신 서비스 약관 페이지에 일괄 표기한다
- 진행도는 수집 격자가 아니라 미션 기간 안에 촬영한 영상으로 센다 (2026-08-15). 수집 격자 (도감 점령)는 촬영 시각을 보지 않아서, 미션이 생기기 전에 그 자리에서 찍어 둔 영상만 있어도 진행도가 1/1로 나가는데 완료 스탬프는 안 나와 카드가 모순으로 보인다. 팝업은 판정 범위 축소 (위 40m 항목)가 들어가기 전까지 900m 사각형이라 밀집 지역에서 이 상태가 흔하다. 그래서 진행도를 스탬프 판정과 같은 술어(기간 안에 촬영한 내 영상이 있는 격자 수)로 통일한다. 대신 예전에 찍은 자리가 지도에서는 자기 색인데 미션 카드는 0으로 보일 수 있고, 이는 "미션 기간에 갔나"를 묻는 값이라 의도된 표시다. 진행도 요구는 SRS FR-MISSION-18이고 구현은 MSG-398이다
9. 미해결 질문¶
- [ ] 종류별 노출 반경 값. MSG-368의 미확정 사항이 그대로 걸린다. 축제와 코스와 팝업 각각의 값은 FE와 상의해 정한다 (SRS 미해결 표 등재)
- [ ] 외부 데이터 이용약관의 출처 표기 조건. TourAPI와 공공데이터가 출처 표기를 조건으로 두는지 확인한다. 조건이 있으면 화면이 아니라 서비스 약관 페이지에 일괄 표기하는 방식으로 푼다 (위 확정 사항의 단서)
- [ ] 묶음 선택 시 목록 좁힘의 재료. 행정 단위 묶음(2026-08-20)을 선택했을 때 목록을 그 미션들로 좁히려면, 집계 응답에 미션 식별자 목록을 실을지 화면이 그 지역 범위로 재조회할지 정해야 한다. MSG-437 스펙에서 결정한다
- [ ] 넓은 축척에서 좌측 목록의 상태. 지도 면은 행정 단위 마커가 채우는데(0.5도 초과), 목록이 확대 안내를 유지할지 묶음 단위 요약을 보여줄지 디자인과 정한다
10. 참고¶
시안 9장이 피그마 "필맵 웹 디자인 ver 12_미션디자인" 페이지의 섹션 "🗺️ 미션 지도 탐색 개편"에 모여 있다. 실제 dev 데이터와 실제 포스터 이미지로 만들었다.
| 화면 | 노드 |
|---|---|
| 목록 · 지역축제 | 14449:1477 |
| 목록 · 팝업스토어 | 14456:1525 |
| 목록 · 경로추천 | 14456:11931 |
| 상세 · 축제 (영상 있음) | 14463:1703 |
| 상세 · 축제 (빈 상태) | 14460:1621 |
| 상세 · 팝업 | 14473:1744 |
| 상세 · 코스 | 14461:1669 |
| 밀집 · 마커와 클러스터 | 14486:1785 |
| 미션 선택 · 판정 범위 | 14487:1833 |
관련 요구사항은 SRS의 FR-MISSION-01, FR-MISSION-02, FR-MISSION-09, FR-MISSION-13이다. 2026-08-20 개정분(행정 단위 묶음)은 FR-MISSION-20(신설)과 FR-MISSION-15(갱신)다. 이 PRD가 승인되면 FR-MISSION-13을 상세화하고, 진행도 노출이 FR-MISSION-02의 "응답에 사용자별 진행도는 없다"와 부딪히므로 그 항목을 함께 정리한다.
[^1]: 뷰포트: 지도에서 지금 화면에 보이는 경위도 범위. 전국 데이터를 다 내려보내는 대신 이 범위로 잘라 응답 크기를 줄인다. [^2]: 전역 캐시: 모든 사용자에게 똑같은 응답이라 한 벌만 저장해 두고 돌려쓰는 방식. 사용자마다 다른 값이 응답에 끼는 순간 이 전제가 깨진다. [^3]: 양자화: 연속된 좌표나 도형을 격자 같은 이산 단위로 바꾸는 것. 축제의 원형 반경이나 코스의 선을 100m 칸 집합으로 옮기는 작업이 여기 해당한다. [^4]: og:image: 웹페이지가 공유 미리보기용으로 스스로 선언해 두는 대표 이미지 주소. 팝업 포스터를 이 값에서 얻는다. [^5]: 핫링크: 남의 서버에 있는 이미지를 내 화면에서 주소로 직접 불러 쓰는 것. 원본이 내려가면 화면이 깨지고 상대 서버에 트래픽을 떠넘기게 된다. [^6]: 폴백: 원래 쓰려던 값이 없을 때 대신 쓰는 값. 이름 없는 경유 지점에 격자 표시명을 넣는 것이 그 예다.