[TIL] Vue - Kakao Map API (2)

jeongjwon·2024년 1월 16일
0

Vue

목록 보기
13/19

참고
https://www.youtube.com/watch?v=Rll7Fy0nI0c
https://apis.map.kakao.com/web/documentation/#Marker



Kakao Map API - Marker

이전 시간에 3가지 정도의 장소를 각각 클릭하면 지도의 중심좌표가 이동되게 하였다. 이번에는 마커를 생성하여 3개의 장소를 마커로 표시되도록 하는 기능을 배웠다.

step 1. Marker 핸들러

Marker 생성

App.vue 의 data 부분에 marker handler 를 null로 생성해준다.



Kakao Map Web API Document 에 따르면 생성자를 이용해 마커를 생성할 수 있다.

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 의 매개변수로 넘겨주고 콘솔에 값이 출력되게 하였다.

handler 와 target 이 객체로 출력되는 것을 볼 수 있다.

marker 추가

값이 잘 넘어오고 있다는 뜻이니, 이전 시간에 마운트시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 2. Marker Click Listener

앞선 step 1에서는 각각의 장소들의 마커를 생성하여 지도에 마커가 보이게 할 수 있도록 했다.
이번 step 에서는 마커를 클릭하면 각각의 장소들이 active 하게끔 구현되게 해보도록 한다.

marker handler옵션 추가

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 콜백이 제공되었다면, 클릭 이벤트 함수를 등록하여, 클릭된 마커에 대한 사용자 데이터를 전달할 수 있도록 한다.




active 효과 주기

마커를 클릭하면 지금은 콘솔을 통해 어디인지 콘솔창에 출력된 place 를 통해 알게 될 것이다. 우리는 place 키워드를 이미 지도 옆에 웹 상으로도 렌더링되어있기 때문에 그 키워드 부분에 active 될 수 있게 하도록 구현하겠다.

변수로 activeHarbor 라는 생성한다.

markerhandler 를 생성할 때 markerClicked 콜백 함수에 현재 선택된 harbor 가 activeHarbor 로 저장되게 한다.

html 에서는 선택된 hbr === activeHarbor 의 조건이 참일 경우 class 가 active 가 추가되도록 하는 동적으로 적용시킨다.

active 클래스에 맞는 css 를 추가로 적용시켜준다.

이를 통해 제주항 마커를 클릭했을 때 class 명이 active 가 추가된 것을 알 수 있다.




메서드 추가 새성

markerClicked 콜백 함수에 this.activeHarbor = harbor 로 설정했는데 이 부분을 아예 showOnMap 이라는 메서드를 통해 설정해주도록 하였다.

showOnMap의 매개변수로 그대로 harbor 를 넣어주어, activeHarbor 와 지도의 중심좌표인 center 를 추가로 설정해주었다.




전체 코드

App.vue
<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>



marker-handler.js
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;










마치며

코드가 길어질 수록 조금 헷갈리기 시작했지만, 마커 부분은 복습할수록 어떤 로직으로 흘러가는 것인지 정확히 알게 되었다. 마커를 각각 생성해주고, 배열로 따로 설정하고 인스턴스를 통해 마커를 표시할 수 있도록 하였다. 그리고 클릭 이벤트도 마찬가지로 콜백함수의 유무에 따라 마커의 정보를 전달해주고, 지도의 중심이 이동될 수 있도록 새로운 메서드들을 추가시켜줌으로써 구현할 수 있었다. 마커 기능은 지도를 이용함에 있어서 유용하게 쓰이기 때문에 활용도가 정말 높은 기능인 것 같아 더욱 알아 보고 싶기도 하다.






0개의 댓글