이 게시판의 특징은 지도로 게시물의 위치를 볼 수 있다는 것이다. 실제 당근마켓을 이용해 보면서 불편했던 점이 '그래서 어디서 판매하는데?' 였다. 그래서 이 게시판을 구현하였고 카카오지도를 이요하여서 지도에 게시물을 나타내는 기능을 구현하였다.

📌 카카오 지도

카카오지도를 적용하는 방법은 다른 포스트를 통해 업로드 하겠다.

거두절미하고 카카오지도를 적용할 때 부딪힌 문제점은 페이징이었다. 다른 지도 기반의 사이트는 어떻게 작동하는지 모르지만 앞서 게시글리스트 관련으로 페이징의 중요성을 겪고나니 이 부분에도 적용해야겠다는 마음이 생겼다. 그래서 생각한 방법은 현재 보고 있는 지도의 위도경도를 구하는 방법이다.

아래 그림과 같이 해당 화면에서 각 꼭지점의 위도, 경도를 쿼리에 전달시켜 DB에서 추출하는 방식을 사용하려했다. 그래서 이 지도가 어떻게 작동하는지 확이해 볼 필요가 있었다.

bounds



검색을 통해 알게된건 bounds 함수였다. 이 함수를 뜯어보았더니 위와 같은 값들이 콘솔에 찍혔다. 각 변수가 의미하는 숫자가 뭔지 알기위해 직접 각 꼭지점의 위도경도를 찍어보았더니 아래와 같은 결과값을 얻을 수 있었다.

ha : 지도 좌측하단 위도
qa : 지도 좌측 하단 경도
oa : 지도 우측 상단 위도
pa : 지도 우측 상단 경도

저 4개의 변수를 param값으로 보내 쿼리의 조건을 이용하여 화면에 보여줄 데이터를 가져오면 되겠다라고 생각을 하였다.

지도에 표시

showMarker JS

/*DB연동 마커 표시*/
function showMarker(bounds) {

    $.ajax({
        method: 'get',
        url: "/api/mapMenu/list",
        data: {startLat: bounds.qa, startLng: bounds.ha, endLat: bounds.pa, endLng: bounds.oa},
        success: function (success) {

            positions.length = 0;
            for (let i = 0; i < success.data.length; i++) {
                positions.push({title: success.data[i].title, latlng: new kakao.maps.LatLng(success.data[i].myLat, success.data[i].myLng), id: success.data[i].id, category : success.data[i].categoryName});
            }

            for (let i = 0; i < positions.length; i++) {
                let imageSrc;
                ...
                
                var marker = new kakao.maps.Marker({
                    map: map,
                    position: positions[i].latlng,
                    title: positions[i].title,
                    image : markerImage
                });
              ...

                /*for문 밖에 있으면 마지막 마커에만 적용*/
                kakao.maps.event.addListener(marker, 'click', clickMarker(map, marker, iwContent, iwPosition));
              ...
            }

        },
        error: function (request, status, error) {
            alert("code: " + request.status + "\n" + "error: " + error);
        }
    });
}

데이터 추출 쿼리
해당 위도 경도사이에 있는 값을 추출하여 id순으로 정렬하였다.

/*지도 출력 리스트*/
    public List<BoardEntity> getMapList(double startLat, double startLng, double endLat, double endLng){
        return queryFactory.selectFrom(QBoardEntity.boardEntity)
                .where(QBoardEntity.boardEntity.status.ne(Status.soldOut)
                        .and(QBoardEntity.boardEntity.myLat.between(startLat,endLat))
                        .and(QBoardEntity.boardEntity.myLng.between(startLng,endLng))
                )
                .orderBy(QBoardEntity.boardEntity.id.desc())
                .fetch();
    }

지도가 드래그 될때 해당 DB를 불러올 수 있도록 드래그 이벤트 안에 showMarker를 구현하였다.

/*드래그 이벤트*/
kakao.maps.event.addListener(map, 'dragend', function () {
    clusterer.clear();
    bounds = map.getBounds();
    showMarker(bounds);
});

클릭이벤트

해당 마커를 표시하였다. 이때 마커들에게 클릭이벤트를 부여하여 해당게시글로 이동하게 설정하였다. 이 과정에서 위 코드에 있는 kakao.maps.event.addListener(marker, 'click', clickMarker(map, marker, iwContent, iwPosition)); 마커에 이벤트를 부여하는 메소드가 for문 밖에 있으면 마지막 마커에만 적용되므로 for문안에 부여하였다.

추가적으로 인포윈도우를 이용하여 해당 마커 클릭 시 제목과 링크를 보여주는 창을 만들었다. 결과는 아래와 같다.

 var marker = new kakao.maps.Marker({
                    map: map,
                    position: positions[i].latlng,
                    title: positions[i].title,
                    image : markerImage
                });
                var iwContent = '<div class="customoverlay" style="padding:5px; width: max-content;">제목 : '+ positions[i].title +'<br><a href="http://localhost:8080/board/content?id=' + positions[i].id + '" style="color:blue" target="_blank">게시글보기</a></div>',
                    iwPosition = positions[i].latlng; //인포윈도우 표시 위치입니다

카테고리별 마커

추가적으로 게시판에는 카테고리가 많이 존재한다. 이를 사용자가 일일이 눌러보며 구분할 수 없으므로 각 카테고리마다 마커의 색을 구분하였다.

for (let i = 0; i < positions.length; i++) {
                let imageSrc;
                switch (success.data[i].categoryName) {
                    case "디지털기기" :
                        imageSrc = "/images/marker_blue.png";
                        break;
                    case "가구/인테리어":
                        imageSrc = "/images/marker_green.png";
                        break;
                    case "생활/가공식품":
                        imageSrc = "/images/marker_navy.png";
                        break;
                    case "스포츠/레저":
                        imageSrc = "/images/marker_orange.png";
                        break;
                    case "게임/취미":
                        imageSrc = "/images/marker_purple.png";
                        break;
                    case "도서/티켓/음반":
                        imageSrc = "/images/marker_scarlet.png";
                        break;
                    case "기타 중고물품":
                        imageSrc = "/images/marker_yellow.png";
                        break;
                }
                var imageSize = new kakao.maps.Size(40, 45), // 마커이미지의 크기입니다
                    imageOption = {offset: new kakao.maps.Point(27, 69)}; // 마커이미지의 옵션입니다. 마커의 좌표와 일치시킬 이미지 안에서의 좌표를 설정합니다.

                var markerImage = new kakao.maps.MarkerImage(imageSrc, imageSize, imageOption);

뿐만 아니라 해당 마커가 어떤 카테고리를 의미하는지 toolTip을 만들었다. 해당 툴팁은 css를 이용하여 구현하였고 아래는 해당 css코드이다.

.marker_tips{
    width: 30px;
    background: transparent;
    display: inline-block;
    cursor:pointer;
    border: 0;
}
.tooltip-content {
    display: none;
    position: absolute;
    max-width: 200px;
    border: 1px solid;
    border-radius: 5px;
    padding: 5px;
    font-size: 0.8em;
    color: white;
    background: #577be7;
}
.marker_tips:hover .tooltip-content {
    display: block;
}
.tooltip-marker {
    width: 18%;
    display: block;
    margin-left: 0;
}

검색 및 현재위치

검색을 구현하여 사용자가 원하는 지역에 있는 게시물을 확인 할 수 있도록 구현하였다. 해당 검색은 카카오데브사이트를 참고하여 사용하였다. 이 때 사용자가 검색버튼을 통한 검색과 엔터키를 통한 검색을 둘다 이용할 수 있도록 두가지 다 구현하였다.

// 키워드 검색 완료 시 호출되는 콜백함수 입니다
function placesSearchCB (data, status) {
    if (status === kakao.maps.services.Status.OK) {
        clusterer.clear();
        // 검색된 장소 위치를 기준으로 지도 범위를 재설정하기위해
        // LatLngBounds 객체에 좌표를 추가합니다
        var bounds = new kakao.maps.LatLngBounds();

        for (var i=0; i<data.length; i++) {
            bounds.extend(new kakao.maps.LatLng(data[i].y, data[i].x));
        }
        // 검색된 장소 위치를 기준으로 지도 범위를 재설정합니다
        map.panTo(bounds);
        showMarker(bounds);
    }
}
/*검색어 자동 초기화 & 엔터 검색*/
$(document).on("keydown",function (a){
    if(!a.altKey && (!a.ctrlKey && !(a.metaKey || "INPUT" == a.target.tagName))){
        /*which : 키 코드 확인가능*/
        var b = a.keyCode || a.which;
        if (47 < b && 58 > b || 64 < b && 91 > b || 95 < b && 106 > b)
            $("#address").val(''),
                $("#address").focus();
    }else if(a.which == 13){
        const keyword = $("#address").val();
        ps.keywordSearch(keyword, placesSearchCB);
        $("#address").blur();
    }
});

추가적으로 사용자의 편의성을 위해 검색 후 인풋창에 커서를 대고 검색어를 입력하였을 때 자동으로 이전 키워드를 지워주는 기능을 위에 추가하엿다.

...
if (47 < b && 58 > b || 64 < b && 91 > b || 95 < b && 106 > b)
            $("#address").val(''),
                $("#address").focus();
                ...

현재위치를 받아오는 부분은 아래와 같이 geolocation 통해 구현하였다. 이때 panTo라는 메소드를 이용하여 가깝게 이동할 수 있도록 구현하였다.

/*현재위치 버튼 클릭*/
$("#location").on('click', function () {
    clusterer.clear();

    /*현재위치 받아오기*/
    function locationLoadSuccess(pos) {
        // 현재 위치 받아오기
        const currentPos = new kakao.maps.LatLng(pos.coords.latitude, pos.coords.longitude);

        // 지도 이동(기존 위치와 가깝다면 부드럽게 이동)
        map.panTo(currentPos);
        bounds = map.getBounds();
        showMarker(bounds);

    };

    function locationLoadError(pos) {
        alert('위치 정보를 가져오는데 실패했습니다.');
    };

    navigator.geolocation.getCurrentPosition(locationLoadSuccess, locationLoadError);

});

클러스터

만일 데이터가 많아지면 지도를 축소할 때 해당 데이터를 전부 보여주기에는 사용자 입장에서 무리가 있다. 이때를 생각해서 클러스터라는것을 적용시켰다.
클러스터 Constructor

var clusterer = new kakao.maps.MarkerClusterer({
    map: map, // 마커들을 클러스터로 관리하고 표시할 지도 객체
    averageCenter: true, // 클러스터에 포함된 마커들의 평균 위치를 클러스터 마커 위치로 설정
    minLevel: 7 // 클러스터 할 최소 지도 레벨
});

지도 마커 생성 부분

🚩 문제점 및 해결방안

처음 구현했던 위도 경도를 이용하여 데이터를 불러오고 클러스터를 적용하는 과정에서 생각지 못한 문제점이 있었다. 만일 사용자가 지도를 드래그 할 시 데이터를 불러오도록 하였는데 드래그 한번 할때마다 마커가 기하급수적으로 늘어나 같은 자리에 중복적으로 태그가 쌓이는 현상이 발생하였다.

아래 사진과 같이 원하는 결과 즉 기대값은 3개인데 드래그를 할 때 마다 마커가 중복되어 숫자가 늘어나는 모습이다.

시행착오 1(실패)
ajax를 실행 후 해당 마커들을 markerArrays 배열에 옮겨 담음
클러스터를 markerArrays에 의해 실행되도록 설정
markerArrays를 지우는 함수 생성
드래그 이벤트가 발생될 때 removeMarker 실행 시키고 마커 표시함수 실행
실패 원인 : 해당 배열을 초기화 해도 지도상에 클러스터는 계속 남아있어서 숫자 증가

(성공)
클러스터를 초기화 해야하기 때문에 기존처럼 코드 작성 후 드래그 이벤트 발생시 클러스터 초기화 함수를 씀

결과

profile
논리적으로 사고하고 해결하는 것을 좋아하는 개발자입니다.

0개의 댓글