[Vue.js] Implementing Vue Kakao Maps 04 - Polyline 01

In a Vue.js-based project, create a route using Polyline provided by the Kakao Maps API. create a class Segment that will be responsible for the route information to be created, and add features such as creating, modifying, and deleting routes.

[Vue.js] Create a route using Kakao Maps Polyline

In Section 1, attach the source code.

Section 2 describes the code.

1. source code

project structure
[PROJECT_ROOT] +- public | +- index.html |} +- src | +- App.vue | +- main.js | +- segment.js | +- package.json +- package.json
  • index.html - template html file provided when creating a project with the vue cli
  • main.js - Application entry point
  • App.vue - Application Root Component
  • segment.js - Ability to manage paths using Polyline

1.1. index.html

import the map library JS using the API KEY for using KAKAO maps.

HTML
<!DOCTYPE html> <html lang=""> <head> <meta charset="utf-8" /> <meta http-equiv="X-UA-Compatible" content="IE=edge" /> <meta name="viewport" content="width=device-width,initial-scale=1.0" /> <link rel="icon" href="<%= BASE_URL %>favicon.ico" /> <title><%= htmlWebpackPlugin.options.title %></title> <!-- import map library --> <script type="text/javascript" src="//dapi.kakao.com/v2/maps/sdk.js?appkey=[insert api key here]" ></script> </head> <body> <noscript> <noscript> <strong >We're sorry but <%= htmlWebpackPlugin.options.title %> doesn't work properly without JavaScript enabled. Please enable it to continue.</strong > </noscript> <div id="app"></div> <!-- built files will be auto injected --> </body> </html>
  • get API KEY after creating an app from Kakao Developer Center

1.2. main.js

use the same file that is generated when you create a project with the VUE CLI.

JAVASCRIPT
import Vue from "vue"; import App from "./App.vue"; Vue.config.productionTip = false; new Vue({ render: (h) => h(App), }).$mount("#app");
  • nothing else touched

1.3. App.vue

delete the file created when creating the project with the vue cli and configure it with the map screen

App.vue
<template> <div id="app <div id="app"> <div class="path-list"> <h3>Paths</h3> <button @click="processNewPath()">New path</button> <div class="list-of-seg"> <div class="segment" v-for="(seg, index) in segments" :key="index"> <h4>{{ seg.name }}</h4> <!-- pathname required--> </div> </div> </div> <div class="map-wrapper" ref="kakaomap"> <button class="btn-commit-seg" v-if="activeSegment" @click="commitPath()"> end</button ><!--Button to end the path--> </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); // create map and return object } methods: { processNewPath() { console.log("[new path] start"); // pass map object this.activeSegment = startSegment(this.mapInstance); }, commitPath() { this.activeSegment.commit(); this.segments.push(this.activeSegment); this.activeSegment = null; // make the button disappear }, }, }; </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%; display: flex; .path-list { width: 240px; } .map-wrapper { flex: 1 1 auto; .btn-commit-seg { position: absolute; top: 5px; left: 5px; z-index: 1000; } } } </style>

1.4. segment.js

Manage routes using polylines provided by the Kako maps API

segment.js
const DEFAULT_KAKAOMAP_CURSOR = 'url("") 7 5,' 'url("http://t1.daumcdn.net/mapjsapi/images/cursor/openhand.cur.ico") 7 5, url("http://t1.daumcdn.net/mapjsapi/images/cursor/openhand.cur.ico"), default'; /** /** The * classes to represent paths */] class Segment { /** * * @param {kakao.maps.Map} mapInstance map instance * @param {object} props route meta information (name distance etc...) */** @param 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: "blue", strokeOpacity: 0.8, strokeStyle: "solid", // solid line }); this.listeners = { click: null }; this.props = props || {}; this.installListeners(); } get name() { return this.props.name || "NO NAME"; } installListeners() { // Register click listeners const adder = (e) => { console.log("[pos]", e.latLng); this.points.push(e.latLng); // register each point this.render(); }; this.listeners.click = adder; window.kakao.maps.event.addListener(this.map, "click", adder); this.map.setCursor("crosshair"); } render() { // plot the path this.poly.setPath(this.points); } commit() { // Finish building the path this.done = true; // need to disable the listener window.kakao.maps.event.removeListener( this.map, "click", this.listeners.click ); // There is no API to get the cursor. // We just get it and restore it like this this.map.setCursor(DEFAULT_KAKAOMAP_CURSOR); console.log("[Dismissed]"); } } let currentSegment; const startSegment = (mapInstance) => { currentSegment = new Segment(mapInstance); return currentSegment; }; export default startSegment;

2. Implementation description

this is a simple application that generates a route as shown below.

  • press the button in the map to start generating a route
  • click, click, click on the locations you want to add to the route
  • click the [End] button to finish creating the route
  • the generated route appears on the left screen

2.1. Start Route button

to create a new route, register a click listener on the button as shown below.

App.vue
<template> <div id="app <div id="app"> <div class="path-list"> <h3>Paths</h3> <button @click="processNewPath()">New path</button> .... </div> </template> <script> import startSegment from "./segment"; export default { ..., data() { return { ..., activeSegment: null, ... }; }, methods: { processNewPath() { console.log("[new path] start"); // pass in a map object this.activeSegment = startSegment(this.mapInstance); }, ... }, }; </script>
  • start path generation when @click="processNewPath()" button is clicked
  • processNewPath() calls startSegment defined in segment.js, which returns an instance (class Segment) representing the path
  • bind a reference to it to the activeSegment variable

2.2. Create the path

actually creating and managing routes (including modifying and deleting them) is the responsibility of segment.js.

in this file, we define a class called Segment to hide our path-related management functions.

JAVASCRIPT
/** * a class to represent paths */** A class to represent paths class Segment { /** * * @param {kakao.maps.Map} mapInstance map instance * @param {object} props route meta information (name distance etc...) */** @param 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: "blue", strokeOpacity: 0.8, strokeStyle: "solid", // solid line }); this.listeners = { click: null }; this.props = props || {}; this.installListeners(); } get name() { return this.props.name || "NO NAME"; } installListeners() { // Register click listeners const adder = (e) => { console.log("[pos]", e.latLng); this.points.push(e.latLng); // register each point this.render(); }; this.listeners.click = adder; window.kakao.maps.event.addListener(this.map, "click", adder); this.map.setCursor("crosshair"); } render() { // plot the path this.poly.setPath(this.points); } commit() { // Finish building the path this.done = true; // need to disable the listener window.kakao.maps.event.removeListener( this.map, "click", this.listeners.click ); // There is no API to get the cursor. // We just get it and restore it like this this.map.setCursor(DEFAULT_KAKAOMAP_CURSOR); console.log("[Dismissed]"); } }

this class currently only implements the "create route" functionality, but it is also responsible for modifying (adding, changing locations) or deleting the created route later.

it is responsible for managing the "state" of the route while it is being created, such as registering and unregistering listeners on the map screen.

  • i want to change the cursor when creating a route
  • i want to draw a clickable marker for each location when editing.

for this reason, we define a separate class to keep the implementation of the various state manipulations hidden from the outside world.

In App.vue, we start a new path as shown below.

App.vue
methods: { processNewPath() { console.log("[new path] start"); // pass in a map object this.activeSegment = startSegment(this.mapInstance); }, ... },

in startSegment in segment.js, we create an instance by passing the passed-in kakaomap map instance to the constructor of class Segment.

Segment constructor
class Segment { /**] * * @param {kakao.maps.Map} mapInstance map instance * @param {object} props route meta information (name distance etc...) */** @param constructor(mapInstance, props) { this.points = []; // list for LatLng this.poly = new window.kakao.maps.Polyline({ map: this.map, path: [], strokeWeight: 2, strokeColor: "blue", strokeOpacity: 0.8, strokeStyle: "solid", // solid line }); ... this.installListeners(); } ... }
  • this.points - Holds the locations (latitude and longitude) clicked on the screen, instances of type kakao.maps.LatLng.
  • this.poly - an instance representing a path. path: [] can be left empty to render the path information anew each time you click on the screen

in installListener() we register a click listener to add clicked locations on the map to the route.

Segment.installListener
class Segment { ... installListeners() { // Register a click listener const adder = (e) => { console.log("[pos]", e.latLng); this.points.push(e.latLng); // register each point this.render(); }; this.listeners.click = adder; window.kakao.maps.event.addListener(this.map, "click", adder); this.map.setCursor("crosshair"); } }
  • const adder = (e) =>{...} Here we register the clicked location value (latitude and longitude) with this.points and call render() to redraw the path to the screen.
  • crosshair - changes the cursor to a cross shape for accurate registration

if you look up the kakao map API, it says to pass the updated route information in an array when redrawing the route.

Segment.render
render() { // Draw the path this.poly.setPath(this.points); }
  • using it as described in the API

2.3. Complete the route

on the map screen, press [Exit] to end the creation.

when you finish creating a route, you need to do the following post-processing.

  • you need to remove the click listener you registered for the map again (otherwise it will keep adding to the route every time you click on the map)
  • the cursor must be returned to its original position

the commit() method is responsible for these tasks.

Segment.commit
class Segment { ... commit() { // Finish building the path this.done = true; // need to remove listener window.kakao.maps.event.removeListener( this.map, "click", this.listeners.click ); // There is no API to get the cursor. // We just get it and restore it like this this.map.setCursor(DEFAULT_KAKAOMAP_CURSOR); console.log("[Dismissed]"); } }

the problem here is that there is no way to get the current cursor from the KAKAO map API.

  • there is no getCursor

to solve this problem, we use the browser DevTool to get the style value for the cursor directly and store it as a variable.

JAVASCRIPT

the completed path is put into an array in App.vue and plotted on the screen (v-for, etc.)

App.vue
<template> <div id="app <div id="app"> <div class="path-list"> ... <div class="list-of-seg"> <div class="segment" v-for="(seg, index) in segments" :key="index"> <h4>{{ seg.name }}</h4> <!-- pathname required--> </div> </div> </div> <div class="map-wrapper" ref="kakaomap"> <button class="btn-commit-seg" v-if="activeSegment" @click="commitPath()"> end</button ><!--Button to end the path--> </div> </div> </template> <script> import startSegment from "./segment"; export default { ... data() { return { ... activeSegment: null, segments: [], // list of pathes }; }, mounted() {...}, methods: { processNewPath() {...}, commitPath() { this.activeSegment.commit(); this.segments.push(this.activeSegment); this.activeSegment = null; // make the button disappear }, }, }; </script>
  • @click="commitPath()" Execute method when exit button is pressed
  • call commit() to release listeners, etc
  • into the array this.segments.

if we change this.activeSegment to null, it will get caught in the v-if conditional we defined in the exit button and disappear from the screen.

HTML
<button class="btn-commit-seg" v-if="activeSegment"...>Exit</button>
  • nULL is also treated as FALSE (JS syntax)