[TIL] Vue - Kakao Map API (3)

jeongjwon·2024년 1월 17일
2

Vue

목록 보기
14/19

참고
https://www.youtube.com/watch?v=EydLtNIqYkw&t=110s
https://apis.map.kakao.com/web/sample/keywordBasic/




Kakao Map API - 키워드로 장소 검색

Kakao Map API 에서 제공하는 키워드로 장소 검색 기능을 사용할 수 있다.



step 1. api 사용법

api library 를 사용하는 것이기 때문에
index.html 파일의 script 부분에 &libraies=services 를 추가한다.



Kakao Map API 문서에서 키워드로 장소검색하기 에 나와있는 예제 코드를 보면 장소 검색 객체를 생성하고 그 키워드로 keywordSearch 메서드를 이용해 장소를 검색할 수 있다. 첫번째 매개변수는 검색하고자 하는 장소이고, 두번째 매개변수는 호출되는 콜백함수로 각각이 가리키는 data, status, pagination 을 출력해보니 다음과 같았다.


여객 터미널을 검색했더니 data는 장소 검색된 배열의 값으로, status 는 결과값에 따른 상태값, pagination 은 그 배열과 관련된 페이지네이션 정보와 같았다.



이런 정보를 가지고 App.vue 폴더에 필요한 코드를 삽입해보겠다.

<div class="searchbox">
        <div>
          <input type="text" value="여객 터미널" @keyup.enter="serachPlace" />
        </div>
</div>

먼저, html 부분에 검색하고자 하는 영역과 input 을 생성하여 검색하고자 하는 값을 enter 키보드 이벤트 핸들러를 추가한다.

 data() {
    return {
      mapOption: {
        center: {
          lat: 33.450701,
          lng: 126.570667,
        },
        level: 8,
      },
      search: {
        keyword: null,
        pgn: null,
        results: [],
      },
    }
 },
 methods: {
    serachPlace(e) {
      const keyword = e.target.value.trim();
      if (keyword.length === 0) return;

      const ps = new window.kakao.maps.services.Places();

      ps.keywordSearch(keyword, (data, status, pgn) => {
        this.search.keyword = keyword;
        this.search.pgn = pgn;
        this.search.results = data;
      });
    },
 }

enter 키보드 이벤트 함수는 input의 e.target.value 값을 받아와 keyword 로 저장한다.
앞서 문서에서 살펴본 예제와 같이 장소 검색 객체를 생성하고 키워드와 콜백함수를 이용해 변수 search 에 해당하는 값들을 저장시킨다.











step 2. 화면 조립 & design

 <div class="searchbox">
        <div>
          <input type="text" value="여객 터미널" @keyup.enter="serachPlace" />
        </div>

        <div class="results">
          <div class="place" v-for="rs in search.results" :key="rs.id">
            <h4>{{ rs.place_name }}</h4>
            <div class="addr">{{ rs.address_name }}</div>
          </div>
        </div>
</div>

html 부분에 키워드 검색 영역과 검색 결과 영역(place_name 과 address_name)을 추가한다.

result 배열의 각각의 값은 다음과 같다.


.map-area {
  display: flex;
  position: relative;
  .searchbox {
    position: absolute;
    top: 0;
    left: 0;
    height: 600px;
    z-index: 10000;
    background-color: #ffffffaa;
    width: 300px;
    display: flex;
    flex-direction: column;
    .results {
      flex: 1 1 auto;
      overflow-y: auto;
      .place {
        padding: 8px;
        cursor: pointer;
        &:hover {
          background-color: rbga(240, 248, 255, 0.657);
        }
        h4 {
          margin: 0;
        }
      }
    }
  }

클래스명에 맞게 css 도 설정해준다.




결과 화면

'여객 터미널' 이라는 검색어를 enter 키를 통해 입력하면 결과값들을 목록으로 한 눈으로 보여진다.











step 3. 장소 클릭으로 화면 이동

장소 클릭으로 화면 이동은 첫번째 Kakao Map API 에서 다루었던 내용이다.
mapOption의 center 중심 좌표를 새로 설정하면 되는 것이었다.

<div class="results">
          <div
            class="place"
            v-for="rs in search.results"
            :key="rs.id"
            @click="showPlace(rs)" 
               //클릭 이벤트 추가
          >
            <h4>{{ rs.place_name }}</h4>
            <div class="addr">{{ rs.address_name }}</div>
          </div>
        </div>
</div>

키워드 검색 결과 영역에 click 클릭 이벤트 핸들러를 추가시켰다.

 showPlace(place) {
      this.mapOption.center = {
        lat: place.y,
        lng: place.x,
 };
  

showPlace 함수는 place 매개변수를 받아 place.y 와 place.x 값을 통해 위도와 경도를 새로 할당시킴으로써 지도의 중심을 바꾸어준다.




결과 화면

목록으로 불러온 값들 중 하나하나의 값을 클릭시 오른쪽의 지도의 중심이 변화되는 것을 볼 수 있다.











전체 코드

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> -->
      <div class="searchbox">
        <div>
          <input type="text" value="여객 터미널" @keyup.enter="serachPlace" />
        </div>

        <div class="results">
          <div
            class="place"
            v-for="rs in search.results"
            :key="rs.id"
            @click="showPlace(rs)"
          >
            <h4>{{ rs.place_name }}</h4>
            <div class="addr">{{ rs.address_name }}</div>
          </div>
        </div>
      </div>
      <KakaoMap ref="kmap" class="kmap" :options="mapOption">
        <div ref="harborOverlay" class="overlay-popup">
          <h3>오버레이 화면</h3>
          <div>이 곳에 항구 정보를 표시함</div>
        </div>
      </KakaoMap>
    </div>
  </div>
</template>

<script>
import KakaoMap from "./components/map/KakaoMap.vue";
import api from "./service/api";
import MarkerHandler from "./components/map/marker-handler";
import KakaoOverlay from "./components/map/overlay";

export default {
  components: {
    KakaoMap,
  },
  data() {
    return {
      mapOption: {
        center: {
          lat: 33.450701,
          lng: 126.570667,
        },
        level: 8,
      },
      search: {
        keyword: null,
        pgn: null,
        results: [],
      },
      // harbors: [],
      // markers: null, //marker handler
      // activeHarbor: null, //selected harbor
      // overlay: null, //overlay instance
    };
  },
  mounted() {
    const vueKakoMap = this.$refs.kmap;
    const harborOverlay = this.$refs.harborOverlay;

    this.markers = new MarkerHandler(vueKakoMap, {
      markerClicked: (harbor) => {
        // console.log("[clicked ]", harbor);
        this.showOnMap(harbor);
        this.overlay.showAt(harbor.lat, harbor.lng);
      },
    });
    console.log(harborOverlay);
    // this.overlay = new KakaoOverlay(vueKakoMap, this.$refs.harborOverlay);
    this.overlay = new KakaoOverlay(vueKakoMap, harborOverlay);

    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: {
    serachPlace(e) {
      const keyword = e.target.value.trim();
      if (keyword.length === 0) return;

      const ps = new window.kakao.maps.services.Places();

      ps.keywordSearch(keyword, (data, status, pgn) => {
        this.search.keyword = keyword;
        this.search.pgn = pgn;
        this.search.results = data;
      });
    },
    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,
      };
    },
    showPlace(place) {
      this.mapOption.center = {
        lat: place.y,
        lng: place.x,
      };
    },
  },
};
</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;
  position: relative;
  .searchbox {
    position: absolute;
    top: 0;
    left: 0;
    height: 600px;
    z-index: 10000;
    background-color: #ffffffaa;
    width: 300px;
    display: flex;
    flex-direction: column;
    .results {
      flex: 1 1 auto;
      overflow-y: auto;
      .place {
        padding: 8px;
        cursor: pointer;
        &:hover {
          background-color: rbga(240, 248, 255, 0.657);
        }
        h4 {
          margin: 0;
        }
      }
    }
  }
  .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;
    .overlay-popup {
      background-color: #ffffffcc;
      box-shadow: 0 0 8px #0000004d, 0 0 1px 2px #00000022;
      max-width: 200px;
      min-width: 160px;
      h3 {
        margin: 0;
        padding: 8px;
        background-color: #ed4215;
        color: white;
        font-weight: 400;
        font-size: 16px;
      }
      .addr {
        padding: 8px;
        white-space: break-spaces;
      }
    }
  }
}
</style>










마치며

라이브러리를 사용할 수 있다는 것에서 카카오는 많은 것을 하게끔 도와주는 것을 알았다. 오늘 step 2 에서 지도 중심이 변화되는 것을 배웠는데, 이를 토대로 해당 장소를 클릭하면 지도의 중심이 변화되면서 마커 또한 생성이 되게 추가로 다루어 보고 싶다.






+ 심화 - 마커 생성

 showPlace(place) {
      this.mapOption.center = {
        lat: place.y,
        lng: place.x,
      };

      this.markers.add(this.search.results, (result) => {
        console.log("[result]", result);
        return { lat: result.y, lng: result.x };
      });
    },

showPlace 함수 내에서 검색한 키워드의 장소들을 markers 에 add 메서드를 통해 장소들을 저장한 배열들을 이용해 지도에 표시되게 하였다.

0개의 댓글