Skip to content

[Work 39] 경로 지도의 참여자 마커 클러스터링과 초기 카메라 맞춤을 구현했습니다. - #31

Merged
snughnu merged 15 commits into
developfrom
WORK-39
Oct 12, 2026
Merged

snughnu merged 15 commits into
developfrom
WORK-39

Conversation

@snughnu

@snughnu snughnu commented Oct 6, 2026 •

Copy link
Copy Markdown
Collaborator

JIRA

📝 작업 내용

📌 요약

  • 경로 화면(참여자 이동 경로)에서 화면상 겹치는 참여자 마커를 홍길동 외 N명 클러스터 마커 하나로 묶어 표시
    • 묶인 멤버를 2초마다 번갈아 보여주고, 슬라이드 전환 적용
    • 마커가 겹칠 때의 우선순위 지정 (클러스터 > 개별 참여자 > 목적지)
  • 경로 화면 진입 시 목적지와 참여자 경로가 한 화면에 보이도록 초기 카메라를 맞춤

🔍 상세

[1] 경로 화면 초기 카메라 로직

  • NMFNaverMapView+Camera.swift 신규 구현
    • MapZoom: 화면 성격별 기본 줌 (여러 지점을 보는 화면 15, 단일 장소 16)
    • moveCamera(to:zoomLevel:): 세 지도 뷰에 각각 있던 동일 구현을 NMFNaverMapView extension 한 곳으로 이동
    • fitCamera(to:padding:maxZoom:): 여러 좌표가 모두 보이도록 카메라 이동
  • 목적지와 모든 참여자의 경로 좌표가 보이도록 최초 1회만 fitCamera 실행
    • 목적지 좌표가 준비된 뒤 참여자가 전달될 때만 실행
    • 이후 데이터가 갱신되어도 카메라를 다시 움직이지 않음

[2] 클러스터 마커 표시

  • ClusterMarkerView: 클러스터 마커 이미지를 그리는 렌더링 전용 뷰 (프로필 원 + 닉네임 외 N명)
  • ClusterMarkerController: 클러스터 마커의 수명주기와 순환/전환 애니메이션을 관리
    • 클러스터 NMFMarker를 표시하고, 멤버 구성이 같은 클러스터는 마커를 재사용 (지도를 움직여도 순환 상태 유지)
    • 2초마다 대표 멤버를 순환하고 CADisplayLink로 약 0.35초 슬라이드 전환.
      • 동작 줄이기가 켜져 있으면 전환 없이 즉시 교체
    • 화면에서 사라지거나 클러스터가 없어지면 순환/전환을 정지하고 정지 이미지로 복원
  • AppointmentRouteMapView
    • 카메라가 멈출 때(mapViewCameraIdle)와 참여자 마커 로딩 직후 클러스터를 다시 계산
    • 묶인 참여자의 개별 마커는 hidden 처리하고, 클러스터 마커 표시는 ClusterMarkerController에 위임.
      • 클러스터가 풀리면 개별 마커로 복귀

💬 리뷰 노트

[1] 클러스터링 구현 방법 후보와 선택한 이유

  • NMapsMap SDK 내장 클러스터러(NMCClusterer)
    • 방법: 마커를 SDK 클러스터러에 등록하면 SDK가 화면 거리 기준으로 묶고,
      클러스터 마커와 개별 마커의 모양은 업데이터(NMCClusterMarkerUpdater, NMCLeafMarkerUpdater)에서 지정
    • 장점
      • 카메라 확대/축소에 맞춰 SDK가 묶임을 갱신하고, 펼침/합침 애니메이션을 제공
      • 개별 마커와 클러스터 마커의 노출 전환을 SDK가 관리해서, 개별 마커 숨김/표시와 재계산 코드가 필요 없음
    • 단점
      • 마커를 직접 소유하고 제어하는 방식보다 구현이 복잡해짐
        (업데이터는 클러스터 정보가 바뀔 때만 호출되고 마커 객체는 SDK 풀에서 재사용되는 구조라서,
        2초마다 멤버를 순환하고 슬라이드 전환을 하려면 현재 노출 중인 클러스터 마커를 별도로 추적/관리해야 함)
      • 묶임 기준이 두 마커 사이의 화면 거리라서, 마커 크기가 겹치는지로 판정하려면 커스텀 거리 전략을 따로 구현해야 함
      • 묶임 결과가 SDK 내부에서 결정되어 코드에서 직접 제어하기 어려움
  • 연결 요소(연쇄 병합)
    • 방법: 서로 겹치는 마커를 이어서, 이어진 마커 전체를 한 클러스터로 묶음
    • 장점: 마커 순서와 상관없이 결과가 같고, 겹치는 마커가 서로 다른 클러스터에 나뉘어 남지 않음
    • 단점
      • A-B, B-C만 겹치고 A-C는 멀어도 A-B-C가 한 덩어리가 됨.
      • 멀리 있는 마커까지 딸려 들어가서 클러스터 범위가 커지고,
        멤버 좌표의 평균으로 잡는 클러스터 위치가 실제 멤버 위치와 크게 달라질 수 있음
  • 거리 기준 greedy (선택)
    • 방법: 마커를 순서대로 하나씩 보면서,
      이미 만들어진 클러스터의 첫 마커(기준 마커)와 화면에서 겹치면 그 클러스터에 넣고,
      겹치는 곳이 없으면 새 클러스터를 시작
    • 장점
      • 방법이 단순함
      • 화면 거리로 비교하므로 줌에 따라 묶임이 자동으로 달라짐
      • 마커 수가 적어 모든 쌍을 비교(O(n^2))해도 부담이 없음
    • 단점
      • 기준 마커와만 비교하므로,
        기준 마커와는 안 겹치고 다른 멤버와만 겹치는 마커는 별도 클러스터가 되어 클러스터 마커끼리 겹쳐 보일 수 있음
      • 마커 순서가 바뀌면 기준 마커가 달라져 결과도 달라질 수 있음
      • 마커가 수천 개 이상으로 늘어나면 비교 횟수가 부담이 됨
    • 선택 이유
      • 화면 거리 기준으로 비교해야 줌인/줌아웃에 따라 묶임이 자동으로 달라짐
      • 참여자 수만큼의 마커만 다루므로 성능을 위한 복잡한 알고리즘이 필요 없고, 단순한 방식으로 충분함
      • 연결 요소는 연쇄 병합으로 클러스터 위치가 크게 어긋날 수 있어서 greedy를 선택함
      • SDK 내장 클러스터러는 줌 대응과 코드량 면에서 장점이 있지만,
        멤버 순환/슬라이드를 마커 풀 구조 위에서 구동하는 복잡도와 묶임 기준의 차이 때문에 채택하지 않고,
        마커를 직접 소유하고 제어하는 방식으로 구현함

[2] 클러스터 표현 정보들

  • 홍길동 외 N명
    • 묶인 인원 수와 대표 이름을 한 번에 보여주기 위함.
    • 닉네임이 길면 닉네임만 말줄임되고 외 N명은 항상 보이도록 라벨을 분리
  • 2초마다 멤버 순환
  • 슬라이드 전환
    • 프로필 원이 오른쪽 아래에서 들어와 왼쪽 위로 밀려나게 해서 멤버가 바뀐 것을 눈으로 알아채기 쉽게 함.
    • 동작 줄이기 설정 시에는 즉시 교체
  • 개별 마커와 같은 NMFMarker + 렌더링 이미지 방식
    • 지도 엔진이 직접 그리므로 이동/줌 중에도 좌표에 붙어 있고, 기존 참여자/목적지 마커와 방식이 일관됨
  • 클러스터 위치와 목적지
    • 클러스터 위치는 멤버 좌표의 평균이고, 목적지 마커는 클러스터링 대상에서 제외해 항상 단독으로 표시
  • 클러스터링 되는 기준
    • 카메라가 멈출 때마다 각 참여자 마커의 현재 화면 좌표(projection)로 판정 (줌아웃하면 묶이고 줌인하면 풀림)
    • 두 마커의 화면 좌표 차이가 가로 < 마커 폭(44pt) && 세로 < 마커 높이(70pt)이면 겹친 것으로 판정
  • 마커가 겹칠 때 위에 올라오는 순서
    • 클러스터 > 개별 참여자 > 목적지 순서
    • 이유
      • 이 화면에서 지도로만 알 수 있는 정보는 참여자의 위치. 목적지는 화면 상단 부제목과 하단 정보 카드에도 표시됨
      • 둘이 겹치는 상황은 보통 참여자가 목적지 근처에 모였을 때이고, 이때는 누가 도착했는지가 더 중요함.
        목적지에 가려지면 가장 궁금한 정보가 사라짐
      • 목적지는 위치가 고정이라 줌인해서 클러스터가 풀리면 다시 보임
      • 여러 명을 대표하는 클러스터가 가려지면 손해가 더 커서 개별 마커보다 위에 둠

📸 영상 / 이미지

build.mp4

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🤖 AI 코드 리뷰 - 아키텍처 관점

참여자 마커 클러스터링 기능을 추가하는 PR이다. moveCamera 중복 구현을 NMFNaverMapView+Camera.swift 익스텐션으로 추출한 것은 적절하다. 클러스터링 관련 타입(ParticipantClusterer, ClusterMarkerView)이 별도 파일로 분리된 점도 긍정적이다. 다만, 클러스터링 로직과 마커 렌더링 상태 관리, CADisplayLink 전환 처리가 모두 AppointmentRouteMapView 안에 집중되어 있어 단일 뷰 클래스의 책임이 크게 늘어났고, 클러스터 회전/전환 타이머 관리 코드가 뷰 계층에 직접 포함되는 구조적 문제가 있다. ParticipantClusterer의 greedy 클러스터링 알고리즘은 리더 마커에만 의존하는 구조로 확장 시 교체가 어렵지 않지만, 현재 구현이 AppointmentRouteMapView에 알고리즘 변경 영향이 직접 미치도록 결합되어 있다.

github-actions[bot]

This comment was marked as duplicate.

github-actions[bot]

This comment was marked as duplicate.

@sangYuLv sangYuLv left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

고생 많으셨습니다!!
늦은 리뷰 죄송합니다 😢

@snughnu
snughnu merged commit 99cc662 into develop Oct 12, 2026
5 checks passed
@snughnu
snughnu deleted the WORK-39 branch October 12, 2026 14:51
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants