티스토리 뷰

728x90
반응형

화면분할 코드(leaflet side-by-side.js)를 공공기관 지도에 활용하기

(feat. 스마트서울맵 SMAP, 통계청 SGIS)

 

 

 

 

몇년 전까지만 해도 leaflet, mapbox 라이브러리를 이용한 웹맵을 많이 개발했었는데,

최근 국내 공공기관에서도 지도 라이브러리들을 만들었다는 것을 알게되었어요.

 

첫번째로, 서울시의 스마트 서울맵 입니다.

https://map.seoul.go.kr/smgis2/

 

스마트서울맵, 더 스마트한 서울지도

스마트서울맵은 도시생활지도, 3D 서울지도, 시민말씀지도, 코로나19 지도, 시민참여지도 등 대표 서울지도 서비스입니다.

map.seoul.go.kr

 

 

스마트서울맵 사이트의 우측상단에 있는 OpenAPI를 통해 스마트서울맵 지도를 사용할 수 있습니다.

기본적인 녹지,한강, 지하철은 당연히 있고, 서울의 대표 관광지들이 지도위에 적혀있는 것을 볼 수 있어요.

 

 

 

 

 

스마트서울맵 SMAP OpenAPI를 신청해 지도를 사용해보니,

이 지도는 leaflet을 활용하여 개발한 지도더군요!

개발자모드를 통해 살펴본 소스에는 leaflet 컨테이너가 존재합니다. 

 

 

 

 

실제로 스마트서울맵 OpenAPI 키를 발급받아 leaflet의 side-by-side.js를 적용해보니 정상적으로 화면분할이 적용되었습니다.

왼쪽에는 스마트서울맵의 기본타일(BASE_MAP_GEN)을, 오른쪽에는 스마트서울맵의 위성타일(BASE_MAP_SATE)을 넣어보았습니다. 

SMAP의 mapsvr.do 파일만 잘 살펴보면, 타일 URL은 쉽게 얻을 수 있습니다.

(URL뿐만 아니라 Resolution, coordinates 등 타일을 정의할 때 필요한 값들이 모두 정의되어 있기에 OpenLayers 같은 타 지도라이브러리에서도 SMAP 타일만 호출하여 사용할 수 있습니다.)

 

 

 

 

 

leaflet 지도에 사용하는 것과 동일하게 적용하되, 타일만 SGIS URL을 넣어주면 되는데요.

 

var myLayer1 = L.tileLayer(...).addTo(map);

var myLayer2 = L.tileLayer(...).addTo(map)

L.control.sideBySide(myLayer1, myLayer2).addTo(map);

 

 

하지만 SMAP 자바스크립트 소스 mapsvr.do 파일을 살펴보니

타일레이어를 "DAWULGIS_EX"라는 함수를 통해 희한하게(?) 타일을 불러오더라구요. (아래 코드 참고)

 

 new L['Proj']['TileLayer'].DAWULGIS_EX(tileUrl, {
    // 타일 옵션 설정
});

 

 

아래는 SMAP 화면분할 하는 전체코드입니다.

 

 

<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<title></title>
	// SMAP의 css, js와 leaflet의 side-by-side.js 코드 불러오기 
	<link rel="stylesheet" type="text/css" href="/css/gisMapCss.css">    
	<script type="text/javascript" src="/js/mapsvr.do"></script> 
	<script type="text/javascript" src="/js/leaflet-side-by-side.js"></script>
	<style>
		html, body, #map{
			width: 100%;
			height: 100%;
		}
		#left, #right{
			z-index: 11115;
		}
	</style>
</head>
<body>
	<div id="map"></div>
	
	<script>
    	// 지도 생성
		map = L['map']('map', {
	        continuousWorld: false,
	        worldCopyJump: false,
	        zoomControl: false,
	        zoomAnimation: true,
	        fadeAnimation: true,
	        inertia: false,
	        closePopupOnClick: false,
	        attributionControl: true,
	        center: [37.54821964955238, 126.985473632812],
	        zoom: 6
	    });
        // SMAP 타일 생성
	    BaseMapChange(map, L['Dawul'].BASEMAP_GEN);
		
        // 화면분할하여 좌/우 팬에 넣을 타일
	    tileUrl1 = BASE_MAP_GEN;
	    tileUrl2 = BASE_MAP_SATE; 
		
        // 왼쪽 팬에는 기본타일을
        var leftPane = new L['Proj']['TileLayer'].DAWULGIS_EX(tileUrl1, {
            zoomControl: false
        });
        map.addLayer(leftPane, true);
        
        // 오른쪽 팬에는 위성타일을
        var rightPane = new L['Proj']['TileLayer'].DAWULGIS_EX(tileUrl2, {
            zoomControl: false
        });
        map.addLayer(rightPane, true);
        
        //side-by-side 적용
        sbs = L['control']['sideBySide'](leftPane, rightPane)['addTo'](map);
        
	</script>
</body>
</html>

 

 

 

국내 공공기관에서 만든 지도 라이브러리 두번째는, 통계청 통계지리정보서비스 SGIS 지도입니다.

지도API 예제 소스들이 있어서, 소스들을 참고하며 개발할 수 있습니다.

 

https://sgis.kostat.go.kr/developer/html/newOpenApi/api/develop/dvp.html

 

개발지원센터

체험하기예제 소스 소스 예제 실행 파라미터 설정 및 소스코드 예제

sgis.kostat.go.kr

 

 

이 SGIS 지도의 장점은 아주 작은 건물 블록 단위로 이루어져있어, 타일이 엄청나게 섬세하다는 것입니다.

SGIS 지도 또한 개발자모드를 살펴보았으나, 어떤 라이브러리를 활용한 것인지 찾아보기가 힘들더라구요.

지도는 자체적으로 이름을 붙인 "sop-container"로 이루어져 있었습니다.

 

 

 

 

SMAP과 동일하게 side-by-side 코드를 적용해보았으나, 오류가 뜨더군요..

leaflet에다 SGIS 타일만 URL로 올릴까... 하다가,

SGIS 지도에 side-by-side.js 코드를 참고하여 화면분할 기능을 만들어보았습니다.

아래부터는 제가 SGIS 지도에 화면분할 기능을 생성한 방법입니다.

 

1. 먼저 SGIS 지도를 생성합니다.

SGIS 지도를 사용하기  위해서는 SGIS에서 인증키를 발급받아야 합니다.

https://sgis.kostat.go.kr/developer/html/newOpenApi/app/rules.html

 

개발지원센터

인증키 신청 SGIS 오픈 플랫폼에서제공하는 API를 이용하기 위한 이용약관입니다. API 이용약관 제 1 조 [목적] 이 이용약관(이하 '약관'이라 합니다)은 통계청(이하 '기관'이라 합니다)과 이용 고객(

sgis.kostat.go.kr

 

 

인증키를 발급받고 나면, 지도API 예제소스를 참고하여 지도를 띄웁니다.

참고로 leaflet, SMAP은 기본 디폴트 좌표계가 EPSG:4326 (WGS84) 이지만, SGIS는 EPSG:5179입니다.

 

 

<!DOCTYPE html>
<html>
<head>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1">
	<title></title>
	<script src="http://code.jquery.com/jquery-latest.min.js"></script>
	<script type="text/javascript" src="https://sgisapi.kostat.go.kr/OpenAPI3/auth/javascriptAuth?consumer_key={인증키}"></script>
	<link rel="stylesheet" type="text/css"  href="/css/sop.css">

	<style>
		html, body, #map{
			width: 100%;
			height: 900px;
		}
	</style>
</head>
<body>
	<div id="map"></div>
    
	<script>
        map = sop.map("map", {
             ollehTileLayer: false,
             scale: false, // 축적 컨트롤
             panControl: false, // 지도이동 컨트롤
             zoomSliderControl: true, // 줌 컨트롤
             measureControl: true, // 측정 컨트롤 (면적, 길이)
             zoomControl:false,
        });
        map.setView(sop.utmk(953702, 1950141), 6);
	</script>
</body>
</html>

 

 

SGIS 지도가 생성되었습니다.

 

 

 

 

 

2. 화면분할에 필요한 태그들을 생성해줍니다.

SGIS 지도에 side-by-side.js 코드와 동일한 기능을 할 수 있도록 태그들을 만들어주어야 합니다.

아래는 leaflet 지도에서 화면분할 기능을 활성화했을때 개발자도구에서 볼 수 있는 코드들입니다.

left, right라는 아이디를 붙였던 팬들이 leaflet-tile-pane에 생성되었고,

leaflet-control-container에 leaflet-sbs 라는 컨트롤러가 생겼습니다.

(아래 이미지에는 left, right div에 스타일이 설정되어있는데, 이부분은 추후에 자동적으로 추가되게 할것이므로 생성할 때 설정해주지 않습니다)

 

left, right pane
leaflet-sbs-controller

 

 

left, right 팬과 sbs controller를 수동으로 만들어줍니다.

원래는 leaflet-tile-pane, leaflet-sbs 지만 SGIS에서는 sop이라는 클래스를 사용하므로 sop으로 이름을 변경해서 만들어주었어요.

 

map.setView(sop.utmk(953702, 1950141), 6);

...

$(".sop-tile-pane").append(`
        <div class="sop-layer" id="left"></div><div class="sop-layer" id="right"></div>
    `);

$(".sop-control-container").append(`
    <div class="sop-sbs">
        <div class="sop-sbs-divider"></div>
        <input class="sop-sbs-range" type="range" min="0" max="1" step="any" >
    </div>
`);

 

개발자도구에서 태그들을 append한 결과를 봅시다.

화면분할기능이 활성화된 leaflet 지도와 동일한 구조를 갖게 되었음을 확인할 수 있습니다.

 

 

 

3. side-by-side.js 코드를 활용한 화면분할 기능 추가

이제 side-by-side.js 코드를 통해 어떤식으로 화면분할 기능이 만들어진 것인지 확인해봅니다.

구글에 leaflet side-by-side.js 라고 치면 제일 첫번째로 나오는 깃헙 링크에서 leaflet-side-by-side.js 코드를 가져왔습니다.

https://github.com/digidem/leaflet-side-by-side/blob/gh-pages/leaflet-side-by-side.js 

 

GitHub - digidem/leaflet-side-by-side: A Leaflet control to add a split screen to compare two map overlays

A Leaflet control to add a split screen to compare two map overlays - GitHub - digidem/leaflet-side-by-side: A Leaflet control to add a split screen to compare two map overlays

github.com

 

 

side-by-side.js 소스코드를 살펴보면, 그중에서도 가장 핵심적인 함수가 4가지 있습니다.

첫번째로, 26번째 줄의 cancelMapDrag는 지도에서는 드래그가 불가능하게(map.dragging.disable()), sbs 컨트롤러만 드래그 되게끔 하는 함수이고,

두번째로, 35번째 줄의 uncalcelMapDrag는 지도에서만 드래그가 가능하고(map.dragging.enable()), sbs 컨트롤러는 드래그가 되지 않게하는 함수입니다.

따라서, 이 두 함수를 이용해서 sbs 컨트롤러를 건드릴 때는 cancelMapDrag를,

sbs 컨트롤러를 건드리지 않을 때는 uncalcelMapDrag를 적용하면 됩니다.

 

 

 

var mapWasDragEnabled
var mapWasTapEnabled


// Line 28
function cancelMapDrag () {
  mapWasDragEnabled = this._map.dragging.enabled()
  mapWasTapEnabled = this._map.tap && this._map.tap.enabled()
  this._map.dragging.disable()
  this._map.tap && this._map.tap.disable()
}

// Line 35
function uncancelMapDrag (e) {
  this._refocusOnMap(e)
  if (mapWasDragEnabled) {
    this._map.dragging.enable()
  }
  if (mapWasTapEnabled) {
    this._map.tap.enable()
  }
}

 

 

세번째로, Line 64의 getPosition는 sbs 컨트롤러의 range value를 가져와 sbs-divider의 left값을 설정해 리턴해주는 함수입니다. sbs-divider는 화면을 좌우로 분할한 중앙선으로, sbs 컨트롤러를 좌우로 움직일때마다 divider도 함께 움직이게끔 보여주기 위함입니다.

(range value는 화면의 가로를 좌표처럼 사용해, 왼쪽으로 갈수록 값은 0이되고, 오른쪽으로 갈수록 값은 1에 가까워지게 됩니다.)

 

마지막으로, Line 123의 _updateClip은 sbs 컨트롤러를 움직일때마다(드래깅) 호출되어야하는 함수인데요.

분할된 좌우 팬을 clip이라는 css를 활용해 분할선에 맞추어 특정영역만 보여주게 해줍니다.

left팬은 중앙선을 기준으로 오른쪽 부분이 가려져 보이지 않게 되고, (clipLeft)

right팬은 중앙선을 기준으로 왼쪽 부분이 가려져 보이지 않게 되는 것입니다. (clipRight)

 

 

// Line 64
getPosition: function () {
    var rangeValue = this._range.value
    var offset = (0.5 - rangeValue) * (2 * this.options.padding + this.options.thumbSize)
    return this._map.getSize().x * rangeValue + offset
  },
  
// Line 123
_updateClip: function () {
    var map = this._map
    var nw = map.containerPointToLayerPoint([0, 0])
    var se = map.containerPointToLayerPoint(map.getSize())
    var clipX = nw.x + this.getPosition()
    var dividerX = this.getPosition()

    this._divider.style.left = dividerX + 'px'
    this.fire('dividermove', {x: dividerX})
    var clipLeft = 'rect(' + [nw.y, clipX, se.y, nw.x].join('px,') + 'px)'
    var clipRight = 'rect(' + [nw.y, se.x, se.y, clipX].join('px,') + 'px)'
    if (this._leftLayer) {
      this._leftLayer.getContainer().style.clip = clipLeft
    }
    if (this._rightLayer) {
      this._rightLayer.getContainer().style.clip = clipRight
    }
  },

 

 

저는 이 4가지 함수를 모두 분리하여 아래와 같이 정의해주었습니다.

 

 

var mapWasDragEnabled
var mapWasTapEnabled


function getPosition() {
    var rangeValue = $(".sop-sbs-range").val();
    var offset = (0.5 - rangeValue) * 42
    return (map.getSize().x) * rangeValue + offset 
}
    
function updateClip(){
    var nw = map.containerPointToLayerPoint([0, 0])
    var se = map.containerPointToLayerPoint(map.getSize())
    var clipX = nw.x + getPosition()
    var dividerX = getPosition()
    $(".sop-sbs-divider")[0].style.left = dividerX + 'px';

    //this.fire('dividermove', {x: dividerX})
    var clipLeft = 'rect(' + [nw.y, clipX, se.y, nw.x].join('px,') + 'px)'
    var clipRight = 'rect(' + [nw.y, se.x, se.y, clipX].join('px,') + 'px)'
    
    $("#left")[0].style.clip = clipLeft
    $("#right")[0].style.clip = clipRight
    
}

// 지도에서 드래그 안됨. sbs-range만 드래그되게끔
function cancelMapDrag () {
  mapWasDragEnabled = map.dragging.enabled()
  mapWasTapEnabled = map.tap && map.tap.enabled()
  map.dragging.disable()
  map.tap && map.tap.disable()

}


function uncancelMapDrag (e) {
  // this._refocusOnMap(e)
  if (mapWasDragEnabled) {
    map.dragging.enable()
  }
  if (mapWasTapEnabled) {
    map.tap.enable()
  }
}

 

 

그리고, 위에서 설명했듯이,

sbs 컨트롤러를 사용할 때는 cancelMapDrag를, 사용하지 않을 때는 uncancelMapDrag를 적용시켜주었고,

sbs range 값이 바뀔때마다 updateClip()을 적용해주었습니다.

또한, sbs컨트롤러를 이용하지 않고 지도를 드래그할 때도 Clip이 변경되어야 하므로 map.on("drag")에도 updateClip를 적용해주어야합니다.

 

 

// range 위에서 드래그하면 아래 함수 실행되도록 해야함.
$(".sop-sbs-range").on("mouseover", function(){
    cancelMapDrag();
});
// range 드래그 끝나면 지도에서 드래그되도록 
$(".sop-sbs-range").on("input change", function(){
    updateClip();
});
// 지도를 드래그해도 left, right 히트맵이미지들의 clip이 변해야함
map.on("drag",function(e) {
    updateClip();
});

$(".sop-sbs-range").on("mouseout", uncancelMapDrag);

 

 

위의 깃헙링크에 존재하는  layout.css와 range.css를 적용하여 아래와 같이 예쁘게 화면분할 기능을 추가했습니다.

 

 

 

 

이제 SGIS 지도 위의 left팬과 right팬에 원하는 마커나 이미지 등을 올렸을 때

sbs 컨트롤러를 이동해보면 왼쪽, 오른쪽 팬 위에 올라간 요소들이 중앙선(sbs-divider)을 기준으로 clip된 것을 확인할 수 있습니다!

SGIS 뿐만 아니라 타 라이브러리에도 사용 가능하니 참고해보세요!

 

 

 

끝입니다!

 

728x90
반응형
댓글