커스텀 오버레이 클러스터링에 대해 질문이 있습니다

[FAQ] 지도/로컬 API 문의 전 꼭 읽어 주세요.
https://devtalk.kakao.com/t/faq-api/125610

안녕하세요 담당자님
저는 현재 Next.js에서 카카오맵을 사용하고 있습니다.
현재 프로젝트에서 Marker대신 CustomOverlay를 사용하여 해당 위치에 있는 장소의 정보(이름, 인원 수)를 제공하고 있습니다.
그리고 클러스터 안에 있는 CustomOverlay들의 인원 수 합을 계산하여 클러스터에 삽입하려고 합니다. (하단 예시 이미지 참조)
제목 없음

이전에는 Markertitle에 값을 주어 ‘clustered’ 이벤트가 발생했을 때 getMarkers() 메소드의 getTitle() 을 사용하여 리턴값을 innerHTML로 삽입시켰습니다.
그런데 CustomOverlay로 변경하면서 title값을 사용할 수 없게 되어서 위 이미지와 같이 클러스터에 기본값으로 설정한 0명으로 표기되고 있습니다.

아래는 현재 제 코드입니다.

CustomOverlay

const [customMarkers, setCustomMarkers] = useState<kakao.maps.CustomOverlay[]>([]);

setCustomMarkers(csMarker => [...csMarker, new kakao.maps.CustomOverlay({
  content: `
    <div style="display: flex; flex-direction: column; align-items: center; width: max-content; height: 52px;">
      <div style="position: relative; display: flex; justify-content: center; align-items: center; padding: 8px; gap: 8px; height: 44px; background-color: var(--gray50); border-radius: 8px; box-shadow: 1px 4px 8px 2px rgba(51, 51, 51, 0.12), 2px 3px 16px 1px rgba(221, 221, 221, 0.08), 2px 3px 6px 3px rgba(230, 230, 230, 0.16);">
        <img src="/images/${distanceSort[i].groupType === "기업" ? "icon_company.png" : "icon_university.png"}" alt=${distanceSort[i].groupType === "기업" ? "기업 아이콘 이미지" : "학교 아이콘 이미지"} style="width: 24px; height: 24px; border-radius: 50%;">
        <div>
          <p class="c1" style="width: fit-content; color: var(--gray900);">${distanceSort[i].name}</p>
          <p class="c2" style="width: fit-content; color: var(--gray700);">${formatNumber(distanceSort[i].people)}명</p>
        </div>
        <div style="position: absolute; width: 12px; height: 10px; left: 50%; bottom: -8px; background-color: var(--gray50); transform: rotate(45deg) translateX(-50%);"></div>
      </div>
    </div>
  `,
  position: new kakao.maps.LatLng(distanceSort[i].lat, distanceSort[i].lng),
  yAnchor: 1
})]);

Cluster

setCluster(new kakao.maps.MarkerClusterer({
  map,
  markers: customMarkers,
  averageCenter: true,
  gridSize: 120,
  minLevel: 2,
  clickable: false,
  hoverable: false,
  texts: function (count: number): string {
    if (defaultTM128.mapx !== 285639 && defaultTM128.mapy !== 540107) {
      return `
        <div style="display: flex; flex-direction: column; justify-content: center; align-items: center; border-radius: 50%; background-color: var(--gray0); width: 100%; height: 100%;">
          <div style="width: 88px; font-size: 16px; font-weight: 600; color: var(--gray800); text-align: center;">0명</div>
          <div style="width: 88px; font-size: 14px; color: var(--gray500); text-align: center;">단체 ${count}개</div>
        </div>
      `;
    } else {
      return `
        <div style="display: flex; flex-direction: column; justify-content: center; align-items: center; border-radius: 50%; background-color: var(--gray0); width: 100%; height: 100%;">
          <div style="width: 88px; font-size: 16px; font-weight: 600; color: var(--gray800); text-align: center;">???명</div>
          <div style="width: 88px; font-size: 14px; color: var(--gray500); text-align: center;">단체 ???개</div>
        </div>
      `;
    }
  },
  styles: [
    {
      display: 'flex',
      justifyContent: 'center',
      alignItems: 'center',
      width: '120px',
      height: '120px',
      background: "radial-gradient(101.47% 101.47% at 27.21% 6.25%, rgba(13, 255, 255, 0.7) 0%, rgba(177, 10, 255, 0.28) 100%)",
      boxShadow: "1px 4px 8px 2px rgba(51, 51, 51, 0.12), 2px 3px 16px 1px rgba(221, 221, 221, 0.08), 2px 3px 6px 3px rgba(230, 230, 230, 0.16)",
      borderRadius: '50%',
      padding: "14px",
      textAlign: 'center'
    }
  ]
}))

Cluster Event

const [cluster, setCluster] = useState<kakao.maps.MarkerClusterer>();

useEffect(() => {
  if (cluster) {
    kakao.maps.event.addListener(cluster, "clustered", function (clusters: kakao.maps.Cluster[]) {
      try {
        for (let i = 0; i < clusters.length; i++) {
          const cls = clusters[i];
          const overlay = cls.getClusterMarker().getContent();
          const getMarker = cls.getMarkers();

          // 현재 동작하지 않음 getTItle에러
          const total = getMarker.reduce((prev, curr) => prev + Number(curr.getTitle().split(": ")[1].replace("명", "")), 0)

          if (typeof overlay !== "string") {
            overlay.firstElementChild!.firstElementChild!.innerHTML = `${total}명`
          }
        }
      } catch (error) {
        console.error(error);
      }
    });
  };
}, [cluster, defaultTM128]);

이에 대해서 두 가지의 질문이 있습니다.

  1. 현재 getMarkers()를 했을 때 CustomOverlay를 사용 중이니 당연히 getTitle()을 하면 오류가 발생합니다. CustomOverlay에서는 특정 값을 호출할 수 있는 방법이 아예 없는 건가요?

  2. 해당 문제를 해결하기 위해 다른 방법을 생각하다가 Marker와 CustomOverlay를 동시에 사용하고 Marker의 opacity를 낮추어 Marker가 존재는 하되 화면에 보이지 않게 하려고 했습니다. 그런데 문제는 동시에 적용이 안되는 것입니다. CustomOverlay가 클러스터링 되지 않아 모든 장소의 정보가 나타나게 됩니다. Marker와 CustomOverlay를 동시에 cluster에서 사용할 수 있는 방법이 있나요?

  1. 커스텀 오버레이의 content를 구성할 때 element요소에 data-*속성을 적용해주세요.
    클러스터러 클릭 시 overay.getContent()로 커스텀 오버레이의elementdata-*속성값을 가져와서
    total값을 적용할 수 있습니다.
    data-* - HTML: Hypertext Markup Language | MDN
    Kakao 지도 Web API Documentation

  2. 마커 클러스터러 사용하기 예제에서 아래 로직으로 수정하면 마커와 커스텀 오버레이가 클러스터러에 추가됩니다.
    클러스터러에 마커와 커스텀 오버레이가 올바르게 추가되었는지 확인해주세요.

// 데이터를 가져오기 위해 jQuery를 사용합니다
// 데이터를 가져와 마커를 생성하고 클러스터러 객체에 넘겨줍니다
$.get("/download/web/data/chicken.json", function(data) {
    // 데이터에서 좌표 값을 가지고 마커를 표시합니다
    // 마커 클러스터러로 관리할 마커 객체는 생성할 때 지도 객체를 설정하지 않습니다
    var markers = $(data.positions).map(function(i, position) {
        return new kakao.maps.Marker({
            position : new kakao.maps.LatLng(position.lat, position.lng)
        });
    });
        
    var overlays = $(data.positions).map(function(i, position) {
        return new kakao.maps.CustomOverlay({
            content: '<div style="background-color:#fff; padding:5px;">'+i+'</div>',
            position : new kakao.maps.LatLng(position.lat, position.lng)
        });
    });

    // 클러스터러에 마커들을 추가합니다
    clusterer.addMarkers(markers);
        
    //클러스터러에 커스텀 오버레이를 추가합니다
    clusterer.addMarkers(overlays);
});

1번 답변을 활용하여 제 쪽에 알맞게 변형시켜 적용하니 잘 되었습니다.
1번이 정상적으로 구현되어 2번 방식을 구현할 필요가 없어졌습니다.
추후 2번 방식이 필요하면 적용시켜보겠습니다.
좋은 답변 감사드립니다.

1개의 좋아요