이 게시판의 특징은 지도로 게시물의 위치를 볼 수 있다는 것이다. 실제 당근마켓을 이용해 보면서 불편했던 점이 '그래서 어디서 판매하는데?' 였다. 그래서 이 게시판을 구현하였고 카카오지도를 이요하여서 지도에 게시물을 나타내는 기능을 구현하였다.
카카오지도를 적용하는 방법은 다른 포스트를 통해 업로드 하겠다.
거두절미하고 카카오지도를 적용할 때 부딪힌 문제점은 페이징
이었다. 다른 지도 기반의 사이트는 어떻게 작동하는지 모르지만 앞서 게시글리스트 관련으로 페이징
의 중요성을 겪고나니 이 부분에도 적용해야겠다는 마음이 생겼다. 그래서 생각한 방법은 현재 보고 있는 지도의 위도경도를 구하는 방법이다.
아래 그림과 같이 해당 화면에서 각 꼭지점의 위도, 경도를 쿼리에 전달시켜 DB에서 추출하는 방식을 사용하려했다. 그래서 이 지도가 어떻게 작동하는지 확이해 볼 필요가 있었다.
검색을 통해 알게된건 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 실행 시키고 마커 표시함수 실행
실패 원인 : 해당 배열을 초기화 해도 지도상에 클러스터는 계속 남아있어서 숫자 증가
(성공)
클러스터를 초기화 해야하기 때문에 기존처럼 코드 작성 후 드래그 이벤트 발생시 클러스터 초기화 함수를 씀
결과