본문 바로가기

📢 들어가며

이번 포스팅은 지난 포스팅에서 이어진다.

지난 포스팅에선 개발 환경 설정을 했었다.
이번 포스팅에선 UI 틀을 잡고 OpenLayers를 활용해 지도를 띄워 볼 것이다.

모든 소스코드는 깃헙에서 확인할 수 있다.

🍜 UI 구성

대략적인 UI를 설계해보았다.

UI 틀 설계

  • 전체 화면에 꽉 차는 느낌으로 지도를 띄운다.
  • 좌측엔 맛집에 대한 정보를 기록/출력하는 사이드 바가 있다.
  • 사이드 바는 드래그로 크기를 늘렸다 키울 수 있다.
  • 사이드 바는 버튼으로 최소/최대화가 가능하다.
  • 사이드 바는 지도 위에 띄우고 Opacity(투명도)를 두어 지도 위에 띄운다는 느낌으로 구현한다.

사이드 바 안에 들어갈 구체적인 내용은 추후 생각해볼 예정이다.
원하는 대로 가능할지는 모르겠지만 대략적인 UI 틀은 이렇다.

🍜 OpenLayers 설치

OpenLayers웹 앱에 동적 지도를 띄우도록 해주는 라이브러리이다.

 

OpenLayers를 설치해보자.

CDN으로 설치해줄 수도 있고, npm 등으로 install 해줄 수 있다.
나는 npm을 활용했다.

 

CDN

<script src="https://cdn.jsdelivr.net/gh/openlayers/openlayers.github.io@master/en/v6.9.0/build/ol.js"></script>

NPM

npm i ol

🍜 OpenLayers 지도 출력하기

지난 포스팅의 설정 상태 그대로라면 frontend/src/App.vue 라는 파일이 있을 것이다.
App.vue 파일은 가장 초기 화면이 되는 파일이다.
이는 main.js에 설정되어 있다.

import Vue from 'vue'
import App from './App.vue'

Vue.config.productionTip = false

new Vue({
  render: h => h(App),
}).$mount('#app')

App.vue 파일을 import 해온 뒤 렌더링 시키고, 파일 내의 id가 'app'인 element를 사용하겠다는 뜻이다.

 

App.vue 파일이 가득차게 지도를 띄울 것이다.

 

MainMap.vue 생성

frontend/src/components 폴더에 MainMap.vue 파일을 파주었다.
그리고 App.vue 파일에 MainMap.vueimport 해오고 아래와 같이 입력해주었다.

<template>
  <div id="app">
    <MainMap/>
  </div>
</template>

<script>
import MainMap from '@/components/MainMap'

export default {
  name: 'App',
  components: {
    MainMap
  }
}
</script>

<style>
#app {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;
}
</style>

frontend/src/App.vue

 

화면을 가득 차게 구현하고 싶기 때문에 positionabsolute로 주고 상하좌우를 모두 0으로 세팅했다.

 

이제 MainMap.vue를 구현하여 지도를 띄워보자.
OpenLayers 공식 문서가 굉장히 잘 되어 있었다.
공식문서를 따라 아래와 같이 입력해주었다.

<template>
  <div class="main-map" ref="map">
  </div>
</template>

<script>
import OlLayerTile from 'ol/layer/Tile.js';
import OlView from 'ol/View.js';
import OlMap from 'ol/Map.js';
import OSM from 'ol/source/OSM';
import {fromLonLat} from 'ol/proj.js'

export default {
  name: 'MainMap',
  data() {
    return {
      olMap: undefined,
    }
  },
  mounted() {
    this.olMap = new OlMap({
      target: this.$refs.map,
      layers: [
          new OlLayerTile({
            source: new OSM()
          })
      ],
      view: new OlView({
        center: fromLonLat([127.1388684, 37.4449168]), // 경기도 성남
        zoom: 10
      })
    })
  }

}
</script>

<style scoped>
.main-map {
  width: 100%;
  height: 100%;
}

</style>

frontend/src/components/MainMap.vue

 

먼저 화면 꽉 차게 지도를 띄우기 위해 widthheight를 모드 100%로 주었다.

 

olMap이라는 data()를 정의하고 OlMap을 생성해 저장한다.
지도와 관련된 모듈은 node_modules에 위치해있는 ol 폴더에 있는 기능 중 필요한 것을 import 해와서 사용했다.

 

OlMap을 생성하는 데에 필요한 옵션은 크게 target, layers, view 가 있다.
target은 이 지도를 띄울 element를 찾아 정의하는 것인데, 공식문서는 id를 활용했으나,
나는 Vue를 사용하기 때문에 ref를 활용했다.


💡 mounted 에 지도를 생성, 정의하는 이유
처음엔 created에 지도 생성 코드를 넣어 줬었는데, 아무 것도 뜨지 않았다.
위처럼 지도가 그려질 위치를 찾는 데에 ref를 사용했다.
ref는 하위 컴포넌트(여기선 <div ref="main-map">의 요소를 사용하기 위해 쓰는 Vue 속성이다.
때문에 하위 컴포넌트가 완전히 렌더링 된 후에 ref로 참조할 수 있어서,
created 에선 ref로 해당 target을 찾을 수 없었던 것이다.
때문에 지도 생성은 mounted에서 진행해야한다.


layers 는 말 그대로 레이어를 의미하는데, 화면에 종이 한장을 얹는다는 의미로 생각하면 되겠다.
이 종이엔 OlLayerTile라는 Tile 형태를 통해 생성된 지도가 그려지게 된다.
지도는 OSM(Open Street Map)이라는 오픈소스를 통해 그린다.

 

view는 사용자 화면에 보여질 위치를 지정하는 옵션이다.
나는 경기도 성남시의 위도 경도를 찾아 입력해줬다.

 

여기서 fromLonLat 은 위도, 경도를 좌표계로 변환시키는 Openlayers 의 api이다.

OpenLayer는 위도, 경도가 아닌 좌표계(coordinate)로 위치가 표현된다. 

default 좌표계 종류는 'EPSG:3857'이다.

 

'EPSG:3857'? 생소한 말처럼 들릴 수 있다 (내가 그랬음...)

해당 용어나 좌표계에 대한 설명은 주소를 검색/입력 받는 기능을 구현하는 다음 포스팅에서 좀 더 자세히 다뤄보겠다.

 

결과화면

정상적으로 성남시쪽 지도가 뜬 것을 확인할 수 있었다!🎉

그런데 좌측 위를 보면 보기 싫은..? 버튼들과 copyright 문구가 보인다.

나중에 추가하는 일이 있더라도, 지금은 이를 없애보자.

 

https://openlayers.org/en/latest/apidoc/module-ol_Map-Map.html

공식 new Map 속성 관련한 공식 문서를 보면, controls라는 옵션이 있는 걸 볼 수 있다.
해석해보면, controls라는 옵션을 따로 추가하지 않으면 defaults 가 사용된다는 것이다.
detaults 가 바로 화면에 보이는 못생긴 버튼과 copyright이다.

 

import 를 추가하고 defaults를 없애주기 위해 controls 옵션을 추가 시켜주자.

// ...
import {defaults} from 'ol/control.js';
// ...
    this.olMap = new OlMap({
      target: this.$refs.map,
      controls : defaults({
        attribution : false,
        zoom : false,
        rotate: false,
      }),
      layers: [
          new OlLayerTile({
            source: new OSM()
          })
      ],
      view: new OlView({
        center: fromLonLat([127.1388684, 37.4449168]), // 경기도 성남
        zoom: 11
      })
    })

//...

frontend/src/components/MainMap.vue

 

attribution, zoom, rotate는 각 detaults에 정의된 각 버튼 및 속성을 의미한다.
전부 false 로 주어 비활성화 시켜줬다.

 

결과화면

버튼과 copyright가 깔끔하게 없어진 것을 확인할 수 있었다.

🍜 사이드바 UI 틀 잡기

지도를 띄웠으니, 이제 설계했던 사이드바 UI를 구현해볼 것이다.

resizable하게 구현하기 전에, 먼저 대략 위치정도만 잡아보자.


frontend/src/componentsSideBar.vue 파일을 추가해주고
App.vue에 import 해주자.
그리고 css로 위치와 크기를 잡는다.

<template>
  <div id="app">
    <MainMap/>
    <SideBar class="side-bar"/>
  </div>
</template>

<script>
import MainMap from '@/components/MainMap'
import SideBar from '@/components/SideBar'

export default {
  name: 'App',
  components: {
    SideBar,
    MainMap
  }
}
</script>

<style lang="scss">
#app {
  position: absolute;
  top: 0;
  right: 0;
  bottom: 0;
  left: 0;

  > .side-bar {
    position: absolute;
    left: 0;
    top: 0;
    bottom: 0;
  }
}
</style>

frontend/src/App.vue

<template>
  <div class="side-bar-wrapper">
    <div class="side-bar">

    </div>
  </div>
</template>

<script>
export default {
  name: 'SideBar',
  data() {
    return {

    }
  },
  methods: {

  }
}
</script>

<style lang="scss" scoped>
.side-bar-wrapper {
  > .side-bar {
    background-color: #000000;
    opacity: 0.5;
    width: 500px;
    height: 100%;
  }
}

</style>

frontend/src/components/SideBar.vue

 

결과화면

설계했던대로 opacity도 주고, 좌측에 위치 시켰다.
지금은 사이드 바의 width가 500px이지만, 이것은 resizable해야한다.
나는 이를 위해 vue-resizable 라이브러리를 사용하기로 했다.

 

vue-resizable 설치

npm i vue-resizable

vue-resiazble을 import 해 온 후, 컴포넌트화 한다.
컴포넌트 해 온 vue-resiazble 하위에 <div class="side-bar">를 넣어보자.

<template>
  <div class="side-bar-wrapper">
    <VueResizable
        class="resizable-side-bar"
        :width="500"
        :min-width="500"
        :max-width="Infinity"
        :active="['r']"
    >
      <div class="side-bar">

      </div>
    </VueResizable>
  </div>
</template>

<script>
import VueResizable from 'vue-resizable';

export default {
  name: 'SideBar',
  components: {
    VueResizable
  },
  data() {
    return {

    }
  },
  methods: {

  }
}
</script>

<style lang="scss" scoped>
.side-bar-wrapper {
  > .resizable-side-bar {
    > .side-bar {
      background-color: #000000;
      opacity: 0.5;
      width: 100%;
      height: 100%;
    }
  }
}
</style>

VueResizble의 옵션엔 여러가지가 있는데 나는 그 중 width, min-width, max-width, active를 추가해줬다.
width, min-width, max-width는 보기만 해도 무슨 뜻인지 짐작이 갈 것이다.
active=['r']r. 즉, 오른쪽(right)의 resize화를 활성화한다는 뜻이다.

 

결과화면

정상적으로 Resize되는 것을 확인할 수 있었다!🎉

 

🚨 참고

TypeError: (0 , i.openBlock) is not a function 에러가 뜨면서 vue-resizable 이 적용이 되지 않는다면 

vue-resizable 을 다운그레이드하면 된다. 

아직 공식적으로 해당 이슈 관련하여 vue-resizable 측의 답변이 달리진 않았으나,

vue2 와 vue-resizable 특정 버전이 서로 호환이 되지 않는 것으로 보인다. 

(관련 이슈 페이지)

 

현재 나는 vue는 v2.6.14, vue-resizable은 v2.0.5 버전을 사용 중인데, 에러 없이 잘 되고 있다.

vue 2 를 쓰고 있다면, vue-resizable v2.0.5, v1.3.2, v2.1.3 버전으로 install 해 보는 것을 추천한다!

 

참고로 어떤 버전이 존재하는 지 잘 모르겠다면,

아래 명령으로 역대 버전들을 확인할 수 있다.

npm info vue-resizable versions

 


이번 포스팅에선 OpenLayers로 지도를 띄우고 UI틀을 잡는 작업을 진행했다.
다음 포스팅에선 사이드바 내에 들어갈 UI를 구현해보겠다. (시간이 남으면 약간의 기능도...)


댓글/하트, 피드백은 언제나 환영입니다! 😇

Seize the day!

Spring MVC | Spring Boot | Spring Security | Mysql | Oracle | PostgreSQL | Vue.js | Nuxt.js | React.js | TypeScript | JSP | Frontend | Backend | Full Stack | 자기계발 | 미라클 모닝 | 일상