클러스터러 이벤트 처리

$(document).ready(function(){
	var regions;
	$.ajax({
		method: "GET",
		url: "/place",
		contentType: "application/json",
		success: function(response){
			regions = response;
			clusterer = groupDo(response);
			
            });
		}
	});
	
	var clusterer;
	$(".group-do").click(() => {
		$.ajax({
			method: "GET",
			url: "/place",
			contentType: "application/json",
			success: function(response){
				clusterer = groupDo(response);
			}
		});
	})
	
	function groupDo(regions){
		var mapContainer = document.getElementById('map'); // 지도를 표시할 div 요소
        var mapOption = {
            center: new kakao.maps.LatLng(36.5, 127.5), // 지도 중심 좌표
            level: 12 // 지도 확대 레벨
        };

        // 지도를 생성합니다.
        var map = new kakao.maps.Map(mapContainer, mapOption);

        // 지역을 그룹화할 객체를 생성합니다.
        var groupedRegions = {};

        // 지역 정보를 그룹화합니다.
        regions.forEach(function (region) {
            if (!groupedRegions[region.doName]) {
                groupedRegions[region.doName] = [];
            }
            groupedRegions[region.doName].push(region);
        });
        
        for (var groupName in groupedRegions) {
        	// 도 별로 그룹핑
            var group = groupedRegions[groupName];
            var resCount = 0;
			var clusterSize = 1;
			for (var i = 0; i < group.length; i++) {
	            resCount += group[i].resCount;
	        }
			
            clusterer = new kakao.maps.MarkerClusterer({
                map: map,
                averageCenter: true,
                clickable: true,
                minLevel: 5,
                minClusterSize: clusterSize,
                texts: [groupName + ' 자원 : ' + resCount + '개'],
                calculator: [1, 2, 3]
            });
            
            var markers = [];
            
            for (var i = 0; i < group.length; i++) {
                var marker = new kakao.maps.Marker({
                    position: new kakao.maps.LatLng(group[i].latitude, group[i].longitude),
                    title: group[i].installPlaceName
                });
                markers.push(marker);
            }
            clusterer.addMarkers(markers);
            
            kakao.maps.event.addListener(clusterer, 'clusterclick', function (cluster) {
                var markersInCluster = cluster.getMarkers();
                console.log(markersInCluster);
                for (var i = 0; i < markersInCluster.length; i++) {
                    var marker = markersInCluster[i];
                    var markerPosition = marker.getPosition(); // 마커의 위치 정보를 가져옵니다.
                    var markerTitle = marker.getTitle(); // 마커의 타이틀을 가져옵니다.
                    
                    // 가져온 정보를 콘솔에 출력합니다.
                    console.log('마커 타이틀: ' + markerTitle);
                    console.log('마커 위치 - 위도: ' + markerPosition.getLat() + ', 경도: ' + markerPosition.getLng());
                }
            });
            
            
        }
        return clusterer;
	}

현재 지역별 현황을 보여주는 지도를 구현하고 있습니다. 현재 제공하는 API에서는 지원을 하지 않아 커스텀하여 도/광역시 단위로는 구현을 하였습니다.
그런데 클러스터러 이벤트 처리를 하는 도중에 모든 방법을 적용해봤으나 클러스터러 안에 있는 객체를 가져오지 못해서 질문드립니다.
addListener에 있는 cluster에서 cluster.getMarkers를 하게 되면 위에서 cluster에 마커를 등록하게 되는데 이 마커 객체들을 가져오지 못합니다. null이 되버립니다. 그래서 마커 등록이 제대로 되지 않는 것도 아니고 객체만 못가져오다 보니 어려움이 있습니다.
지금 구현하고 있는 것이 서울을 클릭하였을 때 서울 안에 있는 자원들의 마커를 가져오고 싶습니다.

안녕하세요~

똑같은 데이터를 이용하여 구현을 할 수 없어서,
groupDo 코드의 부분을 참고하여, 비슷하게 구현을 해보아서 테스트 해봤는데,

여러개의 클러스터러를 만들고, 여기에 각각 clusterclick 이벤트를 붙힌다음에,
클러스터 부분을 선택하면 핸들러의 인자로 넘어오는 cluster객체에서 marker 정보가 나오긴 합니다.

다만 현재 위 코드를 보면,

처음에 스크립트가 실행되면서, place API로부터 resgions 값을 가져오면서, groupDo 함수를 호출하는데,
이때 지도 객체와, 마커클러스터러, 마커들도 생성이 되서 지도에 표출이 될겁니다.

그 뒤에 group-do 버튼을 클릭하면, 다시 데이터를 가져오긴 하는데,
또 지도를 만들고, 클러스터 객체를 또 만들고 마커를 또 만들게 되는데,
그럼 현재 map객체를 2개를 띄우고 계시는 것인데, 그럼 이미 지도 객체는 2개가 중첩되서 그려지고 있다는게 됩니다.

아무튼 그렇다고 한들, 맨 위에 덮어 쓰게 되니 클릭했을때 반응을 할텐데; 콘솔로그에 찍히는 로그값 제공 부탁드립니다. null 값이 어디서 어떻게 null값이 나오는지 알려주시면 감사하겠습니다.

getMakers를 호출할때 아무 값이 안나온다는 것인지, 이벤트 핸들로로부터 내려오는 cluster객체가 null인지요.

안녕하세요. 일단 답변 감사드립니다.

var places = `[[${place}]]`;
$(document).ready(function(){
	
	var mapContainer = document.getElementById('map'); // 지도를 표시할 div 요소
    var mapOption = {
        center: new kakao.maps.LatLng(36.5, 127.5), // 지도 중심 좌표
        level: 12 // 지도 확대 레벨
    };
    // 지도를 생성합니다.
    var map = new kakao.maps.Map(mapContainer, mapOption);
 // 지도 확대 축소를 제어할 수 있는  줌 컨트롤을 생성합니다
    var zoomControl = new kakao.maps.ZoomControl();
    map.addControl(zoomControl, kakao.maps.ControlPosition.RIGHT);
    
    var allMarkers = [];
	var regions = JSON.parse(places);
	var clusterers = groupDo(regions);
	clusterEvent(clusterers);
	markerClickEvent(allMarkers);
	
	function clusterEvent(clusterers){
		for (var groupName in clusterers) {
	        kakao.maps.event.addListener(clusterers[groupName], 'clusterclick', function(cluster) {
	        	console.log(cluster);
	        	var markers = cluster._clusterer._markers;
	        	var placeName = {};
	        	placeName[groupName] = [];
	        	for(var i=0; i<markers.length; i++){
	        		placeName[groupName].push(markers[i].Gb);
	        	}
	        	console.log(placeName[groupName]);
	        });
	    }
	}
	
	function markerClickEvent(allMarkers){
		console.log(allMarkers);
		for(var i = 0; i < allMarkers.length; i++) {
			console.log(allMarkers[i]);
	        for(var j = 0; j < allMarkers[i].length; j++) {
	            var marker = allMarkers[i][j];
	            marker.clicked = false;
	            console.log("i: ", i, " j: " + j);
	            kakao.maps.event.addListener(marker, 'click', (function (marker) {
	                return function () {
	                		var placeName = marker.Gb;
			                var position = marker.getPosition();
			                var lat = position.getLat();
			                var lng = position.getLng();
		
			                var iwContent = `<div style="padding:5px;">${placeName}<br><a href="https://map.kakao.com/link/map/Hello World!,33.450701,126.570667" style="color:blue" target="_blank">큰지도보기</a> <a href="https://map.kakao.com/link/to/Hello World!,33.450701,126.570667" style="color:blue" target="_blank">길찾기</a></div>`;
		
			                var iwPosition = new kakao.maps.LatLng(33.450701, 126.570667);
		
			                // 인포윈도우를 생성합니다
			                var infowindow = new kakao.maps.InfoWindow({
			                    position: iwPosition,
			                    content: iwContent,
			                    removable: true
			                });
			                // 마커 위에 인포윈도우를 표시합니다.
			                infowindow.open(map, marker);	                		
	                };
	            })(marker));
	        }
	    }
	} 

	$(".group-do").click(() => {
		var moveLatLon = new kakao.maps.LatLng(36.5, 127.5);
		map.setCenter(moveLatLon);
		map.setLevel(12);
	})
	
	
	function groupDo(regions){

        // 지역을 그룹화할 객체를 생성합니다.
        var groupedRegions = {};
        // 지역 정보를 그룹화합니다.
        regions.forEach(function (region) {
            if (!groupedRegions[region.doName]) {
                groupedRegions[region.doName] = [];
            }
            groupedRegions[region.doName].push(region);
        });
        
        var clusterers = {};
        var infowindows = [];
        for (var groupName in groupedRegions) {
        	// 도 별로 그룹핑
            var group = groupedRegions[groupName];
            var resCount = 0;
			var clusterSize = 1;
			for (var i = 0; i < group.length; i++) {
	            resCount += group[i].resCount;
	        }
			
			clusterers[groupName] = new kakao.maps.MarkerClusterer({
                map: map,
                averageCenter: true,
                clickable: true,
                minLevel: 5,
                minClusterSize: clusterSize,
                texts: [groupName + ' 자원'],
                calculator: [1, 2, 3]
            });

            var markers = [];
            var positions = [];
            for (var i = 0; i < group.length; i++) {
                var marker = new kakao.maps.Marker({
                    position: new kakao.maps.LatLng(group[i].latitude, group[i].longitude),
                    title: group[i].installPlaceName,
                    clickable: true
                });
                markers.push(marker);
                
                var content = '<div>' + group[i].installPlaceName + '</div>';
                var infowindow = new kakao.maps.InfoWindow({
			        content: content // 인포윈도우에 표시할 내용
    			});
                infowindows.push(infowindow);
                kakao.maps.event.addListener(marker, 'mouseover', makeOverListener(map, marker, infowindow));
                kakao.maps.event.addListener(marker, 'mouseout', makeOutListener(infowindow));
            }
            allMarkers.push(markers);
            clusterers[groupName].addMarkers(markers);
        }
        return clusterers;
	}
	
	// 인포윈도우를 표시하는 클로저를 만드는 함수입니다 
	function makeOverListener(map, marker, infowindow) {
	    return function() {
	        infowindow.open(map, marker);
	    };
	}

	// 인포윈도우를 닫는 클로저를 만드는 함수입니다 
	function makeOutListener(infowindow) {
	    return function() {
	        infowindow.close();
	    };
	}
	
    function updatePlace(place) {
        console.log("업데이트 Place: " + place.installPlaceSn);

        // 기존 테이블 행을 삭제합니다.
        $(".place-table-body").empty();

        // 새로운 데이터를 추가합니다.
        var newRow = $("<tr>")
            .append($("<td>").addClass("place-count").text("1"))
            .append($("<td>").addClass("place-name").text(place.installPlaceName))
            .append($("<td>").addClass("place-address").text(place.installPlaceAddress))
            .append($("<td>").addClass("place-detail-address").text(place.installPlaceDetailAddress))
            .append($("<td>").addClass("place-res-count").text(place.resCount));
        // 테이블의 tbody에 새로운 행을 추가합니다.
        $(".place-table tbody").append(newRow);
    }
    
    $(".place-list-container").on("click", ".place-detail-btn", function(){
    	var clickedRow = $(this).closest("tr");
    	
        var placeName = clickedRow.find(".place-name").text();
        var placeAddress = clickedRow.find(".place-address").text();
		var latitude = clickedRow.find(".latitude").val();
		var longitude = clickedRow.find(".longitude").val();
		
        panTo(latitude, longitude);
    });
    
    function panTo(latitude, longitude) {
        // 이동할 위도 경도 위치를 생성합니다 
        var moveLatLon = new kakao.maps.LatLng(latitude, longitude);
        // 지도 중심을 부드럽게 이동시킵니다
        // 만약 이동할 거리가 지도 화면보다 크면 부드러운 효과 없이 이동합니다
        map.setLevel(3);
        map.panTo(moveLatLon);            
    }  
});
	</script>

오늘 아침에 진행하다가 오류도 많고 말씀하신 것처럼 map객체가 두 개인것을 감안하지 못하여서 코드를 전체적으로 수정하였습니다.
클러스터러 클릭 시 그에 대한 마커들을 가져오고 마커 클릭 시 인포윈도우 까지 띄우도록 구현해 보았습니다. 이제 마커를 클릭하면 해당하는 자원정보들을 보여주는 작업을 하고 있습니다.

마커 클릭 시 Spring으로 비동기 요청을 할 수 있을까요?

먼저

placeName[groupName].push(markers[i].Gb);

이 코드는 이렇게 이용하시면 안됩니다. Gb와 같은 것은 난독화시 생성되는 것으로, 저희가 배포시마다 변경됩니다. 그렇기에 이렇게 사용하시면 추후에 100% 문제가 발생됩니다.

그리고
cluster._clusterer._markers
이렇게 사용하신 이유는 clusterer.getMarkers(), cluster.getMarker() 함수가 동작을 하지 않아, 내부 필드를 직접 접근하시는 것 같으신데;; 언더바가 붙은 변수는 추후 변경될 소지가 존재하는점 참고 바랍니다.
그런데 코드상 getMarker함수가 동작을 안할리가 없는데;;; 음 이부분은 확실히 알 수가 없네요;;

코드를 전체적으로 실행을 해보거나 하진 못합니다. 다만 눈으로 봤을때는 큰 문제는 없어보이긴 합니다. 이전에 비해서 많은 수정이 있긴 하네요 ㅎㅎ

말씀하신, 마커 클릭시 서버로 비동기 요청은 당연히 가능합니다.
마커 클릭이벤트에 작성을 하시면 되고,
지도API에서 제공하는 request 관련 API는 없으나, XMLHttpRequest를 사용하셔도 되고, fetch를 사용하셔도 됩니다.
다만 마커 클릭시라면, A라는 마커를 클릭하면 A요청을 보냈는데, B마커를 바로 연달아 클릭하면 B요청도 보낼 것인데,
이럴때 어떻게 처리를 할것인가를 개발자가 정해야 합니다.
비동기이기 때문에 B마커가 활성화가 되어 있으니, A요청에 대한 응답에 대해서는 별도로 캐싱을 해두고, B요청에 대한 응답만 표시를 한다던가와 같이 처리를 하시면 되실것 같습니다. 이후에 A마커를 선택하면 캐시에서 빼시면 되니까요.
캐시라고 적었지만 Map이나 {} ← 오브젝트를 이용하셔도 되고 상관은 없습니다. 새로고침되면 다 사라지니, Web API로 존재하는 localStorage나 이런 것을 이용하셔도 됩니다.

그리고 한가지 질문과 올려주신 코드에 관련된 부분은 아니지만,

만약에 번들러(Webpack과 같은 모듈러)를 사용하신다면, var ← 라는 예약어 말고, let을 쓰시면 클로저 관련된 코드를 상당 수 제거가 가능합니다. 자바스크립트의 이벤트 핸들러에서 클로져를 쓰는 주된 이유가 스코프 때문인데, let을 쓰면 블락스코프 영역이므로, 클로져 없이 이용하실 수 있습니다.

만약 IE를 반드시 대응해야 하고 + 번들러 없이 HTML파일에 바로 삽입되는 방식의 개발환경이라면 위와 같이 작성하시는게 추후의 크로스브라우징 처리에 더 편하실 수는 있는데,
그게 아니라면 번들러를 사용하여, ES5의 낮은 스펙은 babel을 통해 트랜스파일링 하시고, 그외 나머지 코드는 ES6+ (ES6 이상)의 스펙으로 크롬브라우저 기반으로 가능한 최신스펙으로 작성을 하시는게 추후에 더 도움이 되실 것 같습니다.

아하 카카오맵 API를 처음쓰다보니 새로운 사실들을 많이 알게되었네요. 친절한 답변 감사드립니다.

클로저도 배우게되고 직접접근하는 방법밖에 없드고 생각하여 계속 이렇게 쓸 수 있을거라 생각했는데 아니었네요 ㅎㅎ;

많은 도움 받고 코드를 다시 고쳐보도록 하겠습니다.
감사합니다!!