참고
https://www.youtube.com/watch?v=Rll7Fy0nI0c
https://apis.map.kakao.com/web/documentation/#Marker
이전 시간에 3가지 정도의 장소를 각각 클릭하면 지도의 중심좌표가 이동되게 하였다. 이번에는 마커를 생성하여 3개의 장소를 마커로 표시되도록 하는 기능을 배웠다.
App.vue
의 data 부분에 marker handler 를 null로 생성해준다.
new kakao.maps.Map()
에서 첫번째 매개변수는 지도를 표시할 영역이고, 두번째 매개변수는 지도의 옵션으로 지난 시간에 설정한 지도의 중심좌표 center 와 level 을 뜻한다.
이를 바탕으로 MarkerHandler 라는 생성자를 marker-hanlder.js
파일에 생성해준다.
//marker-handler.js
class MarkerHandler {
constructor(vueKakoMap, options) {
console.log("[vue component]", vueKakoMap);
}
}
App.vue
파일의 html 에서 지도를 가리키는 부분을 ref 로 설정하였고,
//App.vue
mounted : {
const vueKakoMap = this.$refs.kmap;
this.markers = new MarkerHandler(vueKakoMap);
}
마운트 될 때, 그 영역을 vueKakaoMap 변수로 할당받아 MarkerHandler 의 매개변수로 넘겨주고 콘솔에 값이 출력되게 하였다.
값이 잘 넘어오고 있다는 뜻이니, 이전 시간에 마운트시api.js
에서 더미데이터를 뽑아 저장할 때, 추가로 markers 에 더미데이터의 장소들을 추가시켜 마커를 생성하도록 한다.
이 add의 메서드는 marker-handler.js
폴더에서 만들어준다.
add(userData, fnConv)
add 메서드는 MarkerHandler 클래스의 인스턴스가 생성될 때마다 KakoMap 인스턴스와 옵션을 받아와 실제로 지도에 마커를 추가하는 역할을 한다.
userData 는 마커를 생성하기 위한 데이터를 담고 있는 배열로, App.vue
에서는 항구의 정보들이 담긴 배열인 harbors 를 넘겨주고 있다.
fnConv 는 사용자 데이터를 받아서 마커의 위치를 나타내는 좌표로 변환하는 역할을 하는 것으로, 배열들을 for 문으로 각각의 데이터를 가리키는 harbor 의 위도 lat 와 경도 lng 를 추출하고 이를 기반으로 한 좌표 객체를 반환한다.
문서처럼 Kako Maps API 를 사용하여 새로운 마커 인스턴스를 생성하고 앞서 변환된 좌표를 사용하여 마커의 위치를 설정한다. 그리고 markerInstance 에 사용자 데이터를 참조할 수 있도록 추가 정보를 저장한다. 이때, $$ 는 사용자가 markerInstance 에 추가한 데이터를 구분하기 위한 일종의 네이밍 컨벤션이다.
add(userData, fnConv) {
userData.forEach((data) => {
const option = fnConv(data); //this is harbor
const markerInstance = new kakao.maps.Marker({
map: this.vueMap.mapInstance,
position: new kakao.maps.LatLng(option.lat, option.lng),
});
markerInstance.$$ = {
data, //reference to user data
};
}
}
앞선 step 1에서는 각각의 장소들의 마커를 생성하여 지도에 마커가 보이게 할 수 있도록 했다.
이번 step 에서는 마커를 클릭하면 각각의 장소들이 active 하게끔 구현되게 해보도록 한다.
MarkerHandler 의 두번째 매개변수로 options 를 추가한다.
mounted() {
const vueKakoMap = this.$refs.kmap;
this.markers = new MarkerHandler(vueKakoMap, {
markerClicked: (harbor) => {
console.log("[clicked ]", harbor);
},
});
}
제주항 국제 여객 터미널 마커를 클릭했을 때 출력된 콘솔창으로 target object 를 통해 lat, lng, place, seq 등의 정보를 확인할 수 있다.
그렇다면 마운트 시 MarkerHandler 의 두번째 매개변수 markerClicked 메서드도 잘 실행되는 것을 볼 수 있다.
마커 클릭시의 이벤트 함수 가이드로 이를 참조하여 생성자의 add 메서드를 수정하면 다음과 같다.
add(userData, fnConv) {
userData.forEach((data) => {
const option = fnConv(data); //this is harbor
const markerInstance = new kakao.maps.Marker({
map: this.vueMap.mapInstance,
position: new kakao.maps.LatLng(option.lat, option.lng),
});
markerInstance.$$ = {
data, //reference to user data
};
//수정한 부분
if (this.options.markerClicked) {
kakao.maps.event.addListener(markerInstance, "click", () => {
this.options.markerClicked(markerInstance.$$.data);
});
}
});
}
옵션으로 markerClicked 콜백이 제공되었다면, 클릭 이벤트 함수를 등록하여, 클릭된 마커에 대한 사용자 데이터를 전달할 수 있도록 한다.
마커를 클릭하면 지금은 콘솔을 통해 어디인지 콘솔창에 출력된 place 를 통해 알게 될 것이다. 우리는 place 키워드를 이미 지도 옆에 웹 상으로도 렌더링되어있기 때문에 그 키워드 부분에 active 될 수 있게 하도록 구현하겠다.
변수로 activeHarbor 라는 생성한다.
markerhandler 를 생성할 때 markerClicked 콜백 함수에 현재 선택된 harbor 가 activeHarbor 로 저장되게 한다.
html 에서는 선택된 hbr === activeHarbor 의 조건이 참일 경우 class 가 active 가 추가되도록 하는 동적으로 적용시킨다.
이를 통해 제주항 마커를 클릭했을 때 class 명이 active 가 추가된 것을 알 수 있다.
markerClicked 콜백 함수에 this.activeHarbor = harbor
로 설정했는데 이 부분을 아예 showOnMap 이라는 메서드를 통해 설정해주도록 하였다.
showOnMap의 매개변수로 그대로 harbor 를 넣어주어, activeHarbor 와 지도의 중심좌표인 center 를 추가로 설정해주었다.
<template>
<div>
<h3>kakao map demo(center, level)</h3>
<div class="controll">
<button @click="zoom(1)">
<span class="material-symbols"> zoom_in </span>
</button>
<button @click="zoom(-1)">
<span class="material-symbols"> zoom_out </span>
</button>
</div>
<div class="map-area">
<div class="harbors">
<div
class="harbor"
v-for="hbr in harbors"
:key="hbr.seq"
@click="showOnMap(hbr)"
:class="{ active: hbr === activeHarbor }"
>
<h3>{{ hbr.place }}</h3>
</div>
</div>
<KakaoMap ref="kmap" class="kmap" :options="mapOption" />
</div>
</div>
</template>
<script>
import KakaoMap from "./components/map/KakaoMap.vue";
import api from "./service/api";
import MarkerHandler from "./components/map/marker-handler";
export default {
components: {
KakaoMap,
},
data() {
return {
mapOption: {
center: {
lat: 33.450701,
lng: 126.570667,
},
level: 8,
},
harbors: [],
markers: null, //marker handler
activeHarbor: null, //selected harbor
};
},
mounted() {
const vueKakoMap = this.$refs.kmap;
this.markers = new MarkerHandler(vueKakoMap, {
markerClicked: (harbor) => {
console.log("[clicked ]", harbor);
this.showOnMap(harbor);
},
});
api.harbor.all((res) => {
console.log("[항구목록]", res.harbors);
this.harbors = res.harbors;
//create markers
this.markers.add(this.harbors, (harbor) => {
return { lat: harbor.lat, lng: harbor.lng };
});
});
},
methods: {
zoom(delta) {
const level = Math.max(3, this.mapOption.level + delta);
this.mapOption.level = level;
console.log("zoom", this.mapOption.level);
},
showOnMap(harbor) {
this.activeHarbor = harbor;
console.log(harbor);
this.mapOption.center = {
lat: harbor.lat,
lng: harbor.lng,
};
},
},
};
</script>
<style lang="scss">
button {
border: 1px solid transparent;
padding: 6px;
background-color: #efefefdd;
border-radius: 6px;
&:hover {
background-color: #ddd;
border-color: #ddd;
cursor: pointer;
}
&:active {
background-color: #aaa;
border-color: #aaa;
}
}
.map-area {
display: flex;
.harbors {
.harbor {
padding: 10px;
border: 1px solid transparent;
&:hover {
background-color: aliceblue;
border-color: #6a9dff;
cursor: pointer;
}
&:active {
background-color: rgb(166, 197, 224);
border-color: #4471c5;
}
&.active {
background-color: rgb(253, 229, 150);
border-color: rgb(211, 173, 3);
}
h4 {
margin: 0;
}
}
}
.kmap {
flex: 1 1 auto;
}
}
</style>
const kakao = window.kakao;
class MarkerHandler {
constructor(vueKakoMap, options) {
console.log("[vue component]", vueKakoMap, options);
this.vueMap = vueKakoMap;
this.options = options;
}
add(userData, fnConv) {
userData.forEach((data) => {
const option = fnConv(data); //this is harbor
const markerInstance = new kakao.maps.Marker({
map: this.vueMap.mapInstance,
position: new kakao.maps.LatLng(option.lat, option.lng),
});
markerInstance.$$ = {
data, //reference to user data
};
if (this.options.markerClicked) {
kakao.maps.event.addListener(markerInstance, "click", () => {
this.options.markerClicked(markerInstance.$$.data);
});
}
});
}
}
export default MarkerHandler;
코드가 길어질 수록 조금 헷갈리기 시작했지만, 마커 부분은 복습할수록 어떤 로직으로 흘러가는 것인지 정확히 알게 되었다. 마커를 각각 생성해주고, 배열로 따로 설정하고 인스턴스를 통해 마커를 표시할 수 있도록 하였다. 그리고 클릭 이벤트도 마찬가지로 콜백함수의 유무에 따라 마커의 정보를 전달해주고, 지도의 중심이 이동될 수 있도록 새로운 메서드들을 추가시켜줌으로써 구현할 수 있었다. 마커 기능은 지도를 이용함에 있어서 유용하게 쓰이기 때문에 활용도가 정말 높은 기능인 것 같아 더욱 알아 보고 싶기도 하다.