[TIL] Vue - Kakao Map API (4)

jeongjwon·2024년 1월 18일
0

Vue

목록 보기
15/19

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






Kakao Map API(4) - Polyline

지도의 필수요소라고 할 수 있는 '길찾기' 기능이 있는데, 지도상에서 직접 클릭을 통해 직선을 그어 경로를 생성하고 삭제할 수 있도록 구현을 해보았다.
Vue.js 기반 프로젝트에서 Kakao Map api에서 제공하는 Polyline을 사용해서 경로를 생성한다. 생성할 경로 정보를 담당할 클래스 Segment를 작성해서 경로 생성 및 수정 삭제등의 기능을 추가한다.






step 1. 경로 생성

새로운 경로 버튼과 지도를 화면에 채운다.

새로운 경로 버튼을 클릭하면 지도에 지점들을 다 클릭할 수 있도록 할 것이고, 지점들의 선택이 완료가 된다면 종료버튼을 눌러 다시 첫번째 화면으로 돌아갈 수 있도록 한다.

<template>
  <div id="app">
    <div class="path-list">
      <h3>경로</h3>
      <button @click="processNewPath()">새로운 경로</button>
    </div>
    <div class="map-wrapper" ref="kakaomap">
      <button class="btn-commit-seg" v-if="activeSegment" @click="commitPath()">
        종료</button
      ><!-- 경로 종료할 때 버튼 -->
    </div>
  </div>
</template>




segment.js

Kakao Map API 문서에서 생성할 경로 정보를 담당할 클래스 Segment를 작성해서 경로 생성할 수 있다.

//segment.js
/*
 *   경로를 나타내는 클래스
 */
class Segment {
  /**
   *
   * @param {kakao.maps.Map} mapInstance 지도 인스턴스
 */
  constructor(mapInstance, props) {
    this.map = mapInstance;
 }
}

let currentSegment;
const startSegment = (mapInstance) => {
	currentSegment = new Segment(mapInstance);
  return currentSegment;
};
export default startSegment;


App.vue
 data() {
    return {
      mapInstance: null, //지도 인스턴스
       activeSegment: null, //종료 버튼 활성화
    }
 },
  mounted() {
    //init map here
    var container = this.$refs.kakaomap;
    var options = {
      center: new window.kakao.maps.LatLng(33.450701, 126.570667),
      level: 3,
    };

    //지도 생성 및 객체 리턴
    this.mapInstance = new window.kakao.maps.Map(container, options);
  },
  
  methods: {
    processNewPath() {
      console.log("[new path] start");
      //지도 객체 전달
      this.activeSegment = startSegment(this.mapInstance);
      //startSegment 객체 생성 -> activeSegment 존재 -> 확인 버튼 활성화
    },
  },

마운트 될 때 지도를 생성하여 화면에 렌더링할 수 있도록 하고,
새로운 경로 버튼 클릭 이벤트가 발생하면 processNewPath 메서드를 통해 Segment 객체가 생성이 되고 activeSegment 로 저장하여 v-if 바인딩을 통해 확인 버튼이 활성화되게 실행한다.











step 2. 경로 추가 - 지도 클릭 이벤트

Segment.js
/*
 *   경로를 나타내는 클래스
 */
class Segment {
  /**
   *
   * @param {kakao.maps.Map} mapInstance 지도 인스턴스
 */
  constructor(mapInstance, props) {
    this.map = mapInstance;
    this.done = false; //+추가) 경로 작성 완료 전
    this.points = []; //+추가) list for LatLng
    this.poly = new window.kakao.maps.Polyline({
      map: this.map,
      path: [],
      strokeWeight: 2,
      strokeColor: "#FF00FF",
      strokeOpacity: 0.8,
      strokeStyle: "solid", //실선
    }); //+추가) 폴리라인 객체 생성
    this.listeners = {};
    this.installListners();  //+추가) 클릭 이벤트 등록
 }
  installListners() {
    //클릭 리스너 등록
    const adder = (e) => {
      console.log("[pos]", e.latLng);
      this.points.push(e.latLng); //각 점을 등록
      this.render();
    };

     this.listeners.click = adder;
    window.kakao.maps.event.addListener(this.map, "click", adder);
  
  }
render() {
    //경로를 그림
    this.poly.setPath(this.points);
  }
//경로 추가 메서드
 commit() {
    //경로 작성 완료
    this.done = true;
    //리스너 해제해야함
    window.kakao.maps.event.removeListener(
      this.map,
      "click",
      this.listeners.click
    );
    //커서를 가져오는 API가 없기에
    this.map.setCursor(DEFAULT_KAKAOMAP_CURSOR);
    console.log("해제 완료");
  }
}
  1. Segment 생성자에서 경로 작성 완료 전의 의미라는 done 변수를 false 로 잡아두고, commit 메서드에서는 완료로 처리되어야 하기 때문에 true 로 변경해준다.

  2. 새로운 경로 버튼 클릭을 하고 맵상에서 위치를 클릭하면 각각의 해당 lat 와 lng 가 출력되는 것을 볼 수 있다. 이를 위도와 경로의 배열을 나타내는 points 변수에 저장시킨다.

  3. 지도 클릭 이벤트를 등록하려면 아래와 같은 이벤트와 콜백함수를 추가시켜준다.

  4. 주어진 객체로 폴리라인을 생성한다. 옵션에서 map 과 path 는 각각 지도와 선택된 지점들의 배열을 가리킨다.

  5. 설정한 polyline 을 이용해 경로를 그리기 위해 setPath 메서드를 활용한다.

  6. commit 메서드에서는 installListners 메서드와는 다르게 클릭 리스너를 해제시킨다.



App.vue
methods: {
  commitPath() {
      this.activeSegment.commit();
      this.segments.push(this.activeSegment);
      this.activeSegment = null; //버튼 사라지게 함
    },
}










step 3. 경로 목록 렌더링

App.vue
data() {
    return {
      mapInstance: null,
      activeSegment: null,
      segments: [], //+추가) list of pathes
    }
}

경로들의 목록을 segments 변수로 저장한다.

  methods: {
    processNewPath() {
      console.log("[new path] start");
      //지도 객체 전달
      this.activeSegment = startSegment(this.mapInstance);
    },
    commitPath() {
      this.activeSegment.commit();
      this.segments.push(this.activeSegment); //+추가) 
      this.activeSegment = null; //버튼 사라지게 함
    },
  }

commitPath 메서드에서 activeSegment을 null하기 전에 segments 배열에 activeSegment 들을 추가시킨다.

<template>
  <div id="app">
    <div class="path-list">
      <h3>경로</h3>
      <button @click="processNewPath()">새로운 경로</button>
      
     <!-- 경로 목록 렌더링 -->
      <div class="list-of-seg">
        <div class="segment" v-for="(seg, index) in segments" :key="index">
          <h4>{{ seg.name }}</h4>
          <!-- 경로 이름 필요함 -->
        </div>
      </div>
      <!-- 경로 목록 렌더링 -->
      
    </div>
    <div class="map-wrapper" ref="kakaomap">
      <button class="btn-commit-seg" v-if="activeSegment" @click="commitPath()">
        종료</button
      ><!-- 경로 종료할 때 버튼 -->
    </div>
  </div>
</template>


segment.js
const DEFAULT_KAKAOMAP_CURSOR =
  "url(&quot;http://t1.daumcdn.net/mapjsapi/images/2x/cursor/openhand.cur.ico&quot;) 7 5, url(&quot;http://t1.daumcdn.net/mapjsapi/images/2x/cursor/openhand.cur.ico&quot;), default";
/*
 *   경로를 나타내는 클래스
 */
class Segment {
  /**
   *
   * @param {kakao.maps.Map} mapInstance 지도 인스턴스
   * @param {object} props 경로 메타 정보들(이름 거리 등..)
   */
	constructor(mapInstance, props) {
    this.map = mapInstance;
    this.done = false;
    this.points = []; //list for LatLng
    this.poly = new window.kakao.maps.Polyline({
      map: this.map,
      path: [],
      strokeWeight: 2,
      strokeColor: "#FF00FF",
      strokeOpacity: 0.8,
      strokeStyle: "solid", //실선
    });
    this.listeners = {};
    this.props = props || {};
    this.installListners();
  }
  get name() {
    return this.props.name || "NO NAME";
  }
  
  installListners() {
    //클릭 리스너 등록
    const adder = (e) => {
      console.log("[pos]", e.latLng);
      this.points.push(e.latLng); //각 점을 등록
      this.render();
    };
    // this.listeners.click = { click: null };
    this.listeners.click = adder;
    window.kakao.maps.event.addListener(this.map, "click", adder);
    
    //+추가) 마우스 커서 설정
    this.map.setCursor("crosshair"); 
  }
  
   commit() {
    //경로 작성 완료
    this.done = true;
    //리스너 해제해야함
    window.kakao.maps.event.removeListener(
      this.map,
      "click",
      this.listeners.click
    );
    //+추가) 커서를 가져오는 API가 없기에
    this.map.setCursor(DEFAULT_KAKAOMAP_CURSOR);
    console.log("해제 완료");
  }
}
  1. segment 의 생성자 매개변수에 props 를 받아와 name을 출력하게 한다.
  1. 클릭 리스너가 진행중일 때, 마우스 커서가 십자 corsshair 가 되도록 변경한다.

  2. 경로 종료 후 마우스 커서를 원위치로 되돌리기 위해서 임의로 마우스 커서의 위치를 따와 DEFAULT 변수로 만들어 commit 메서드에서 setCuror 재설정한다.











전체코드

App.vue
<template>
  <div id="app">
    <div class="path-list">
      <h3>경로</h3>
      <button @click="processNewPath()">새로운 경로</button>
      <div class="list-of-seg">
        <div class="segment" v-for="(seg, index) in segments" :key="index">
          <h4>{{ seg.name }}</h4>
          <!-- 경로 이름 필요함 -->
        </div>
      </div>
    </div>
    <div class="map-wrapper" ref="kakaomap">
      <button class="btn-commit-seg" v-if="activeSegment" @click="commitPath()">
        종료</button
      ><!-- 경로 종료할 때 버튼 -->
    </div>
  </div>
</template>

<script>

import startSegment from "./segment";

export default {
  name: "App",
  components: {

  },
  data() {
    return {
      mapInstance: null,
      activeSegment: null,
      segments: [], //list of pathes

    };
  },
  mounted() {
    //init map here
    var container = this.$refs.kakaomap;
    var options = {
      center: new window.kakao.maps.LatLng(33.450701, 126.570667),
      level: 3,
    };

    //지도 생성 및 객체 리턴
    this.mapInstance = new window.kakao.maps.Map(container, options);

  },
  methods: {
    processNewPath() {
      console.log("[new path] start");
      //지도 객체 전달
      this.activeSegment = startSegment(this.mapInstance);
    },
    commitPath() {
      this.activeSegment.commit();
      this.segments.push(this.activeSegment);
      this.activeSegment = null; //버튼 사라지게 함
    },
  },
};
</script>

<style lang="scss">
html,
body {
  height: 100%;
  margin: 0;
}
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  color: #2c3e50;
  height: 100%;
  width: 100%;
  display: flex;
  flex-direction: column;
  .path-list {
    width: 240px;
  }
  .map-wrapper {
    flex: 1 1 auto;
    .btn-commit-seg {
      position: absolute;
      top: 5px;
      left: 5px;
      z-index: 1000;
    }
  }
}
</style>



segment.js
const DEFAULT_KAKAOMAP_CURSOR =
  "url(&quot;http://t1.daumcdn.net/mapjsapi/images/2x/cursor/openhand.cur.ico&quot;) 7 5, url(&quot;http://t1.daumcdn.net/mapjsapi/images/2x/cursor/openhand.cur.ico&quot;), default";
/*
 *   경로를 나타내는 클래스
 */
class Segment {
  /**
   *
   * @param {kakao.maps.Map} mapInstance 지도 인스턴스
   * @param {object} props 경로 메타 정보들(이름 거리 등..)
   */

  constructor(mapInstance, props) {
    this.map = mapInstance;
    this.done = false;
    this.points = []; //list for LatLng
    this.poly = new window.kakao.maps.Polyline({
      map: this.map,
      path: [],
      strokeWeight: 2,
      strokeColor: "#FF00FF",
      strokeOpacity: 0.8,
      strokeStyle: "solid", //실선
    });
    this.listeners = {};
    this.props = props || {};
    this.installListners();
  }
  get name() {
    return this.props.name || "NO NAME";
  }
  installListners() {
    //클릭 리스너 등록
    const adder = (e) => {
      console.log("[pos]", e.latLng);
      this.points.push(e.latLng); //각 점을 등록
      this.render();
    };
    // this.listeners.click = { click: null };
    this.listeners.click = adder;
    window.kakao.maps.event.addListener(this.map, "click", adder);
    this.map.setCursor("crosshair");
  }
  render() {
    //경로를 그림
    this.poly.setPath(this.points);
  }
  commit() {
    //경로 작성 완료
    this.done = true;
    //리스너 해제해야함
    window.kakao.maps.event.removeListener(
      this.map,
      "click",
      this.listeners.click
    );
    //커서를 가져오는 API가 없기에
    this.map.setCursor(DEFAULT_KAKAOMAP_CURSOR);
    console.log("해제 완료");
  }
}
let currentSegment;
const startSegment = (mapInstance) => {
  currentSegment = new Segment(mapInstance);
  return currentSegment;
};
export default startSegment;





결과화면

0개의 댓글