Source: uGisBaseMap/uGisBaseMap.js

( function() {
	"use strict";

	/**
	 * uGisMapPlatForm 배경지도 객체.
	 * 
	 * 다양하게 제공되는 지도 API나 WMTS 서비스를 배경지도로 사용할 수 있다.
	 * 
	 * uGisMapPlatForm에서 기본적으로 내장한 배경지도 API는 다음과 같으며, API KEY가 정상적인 경우에만 사용할 수 있다.
	 * 
	 * 1. Google(normal, terrain, satellite, hybrid) : 월 28,500건 무료.
	 * 
	 * 2. OpenStreetMap(normal, gray) : 무제한 무료.
	 * 
	 * 3. Stamen(toner, terrain) : 무제한 무료.
	 * 
	 * 4. vWorld(normal, gray, satellite, hybrid, midnight) : 무제한 무료.
	 * 
	 * 5. 바로E맵(normal, white, colorVision) : 무제한 무료.
	 * 
	 * 6. 네이버(normal, satellite, hybrid, terrain) : 무료.
	 * 
	 * 7. 다음(normal, satellite, hybrid) : 월 300,000건 무료.
	 * 
	 * 8. Bing(normal, aerial, hybrid, dark) : 1년 125,000건 무료.
	 * 
	 * @example
	 * 
	 * <pre>
	 * var ugBaseMap = new ugmp.baseMap.uGisBaseMap( {
	 * 	target : 'base',
	 * 	uGisMap : new ugmp.uGisMap({...}),
	 * 	baseMapKey : 'google_normal'
	 * 	useElementMargin : false
	 * } );
	 * </pre>
	 * 
	 * @constructor
	 * 
	 * @param opt_options {Object}
	 * @param opt_options.target {String} 배경지도 DIV ID.
	 * @param opt_options.uGisMap {ugmp.uGisMap} {@link ugmp.uGisMap} 객체.
	 * @param opt_options.baseMapKey {String} 배경지도 Key ( _로 구분 ). Default is `osm_normal`.
	 * @param opt_options.useElementMargin {Boolean} 배경지도 회전 시 공백 처리를 위한 element의 여백 사이즈 사용 유무 . Default is `true`.
	 * 
	 * @class
	 */
	ugmp.baseMap.uGisBaseMap = ( function(opt_options) {
		var _self = this;

		this.target = null;
		this.uGisMap = null;
		this.useElementMargin = null;

		this.UUID = null;
		this.nowMapView = null;
		this.baseMapList = null;
		this.nowBaseMapKey = null;

		this.key_changeCenter = null;
		this.key_elementResize = null;
		this.key_changeRotation = null;
		this.key_changeResolution = null;


		/**
		 * Initialize
		 */
		( function() {

			var options = opt_options || {};

			_self.UUID = ugmp.util.uGisUtil.generateUUID().split( "-" )[ 0 ];
			_self.target = ( options.target !== undefined ) ? options.target : undefined;
			_self.uGisMap = ( options.uGisMap !== undefined ) ? options.uGisMap : undefined;
			_self.nowBaseMapKey = ( options.baseMapKey !== undefined ) ? options.baseMapKey : "osm_normal";
			_self.useElementMargin = ( options.useElementMargin !== undefined ) ? options.useElementMargin : true;

			if ( !_self.uGisMap ) {
				ugmp.uGisConfig.alert_Error( "uGisMap undefined" );
				return false;
			}

			_self.addBaseMapType( "osm", new ugmp.baseMap.uGisBaseMapOSM() );
			_self.addBaseMapType( "bing", new ugmp.baseMap.uGisBaseMapBing() );
			_self.addBaseMapType( "daum", new ugmp.baseMap.uGisBaseMapDaum() );
			_self.addBaseMapType( "naver", new ugmp.baseMap.uGisBaseMapNaver() );
			_self.addBaseMapType( "google", new ugmp.baseMap.uGisBaseMapGoogle() );
			_self.addBaseMapType( "vWorld", new ugmp.baseMap.uGisBaseMapVWorld() );
			_self.addBaseMapType( "stamen", new ugmp.baseMap.uGisBaseMapStamen() );
			_self.addBaseMapType( "baroEmap", new ugmp.baseMap.uGisBaseMapBaroEmap() );

			_self._callBaseMapType( _self.nowBaseMapKey );

			_self.setVisible( true );
		} )();
		// END initialize


		return {
			_this : _self,
			remove : _self.remove,
			setVisible : _self.setVisible,
			getVisible : _self.getVisible,
			getApiMap : _self.getApiMap,
			setOpacity : _self.setOpacity,
			getOpacity : _self.getOpacity,
			visibleToggle : _self.visibleToggle,
			changeBaseMap : _self.changeBaseMap,
			addBaseMapType : _self.addBaseMapType,
			getSelectedBaseMap : _self.getSelectedBaseMap,
			getUsableBaseMapList : _self.getUsableBaseMapList
		}

	} );


	/**
	 * 초기화
	 * 
	 * @private
	 */
	ugmp.baseMap.uGisBaseMap.prototype._callBaseMapType = function(baseMapKey_) {
		var _self = this._this || this;

		if ( !_self._isBaseMapUsable( baseMapKey_ ) ) {
			ugmp.uGisConfig.alert_Error( baseMapKey_ + " undefined" );
			return false;
		}

		$( "#" + _self.target ).prepend( $( "<div>", {
			'id' : _self.UUID,
			'style' : "width: 100%; height: 100%; position: relative; overflow: hidden"
		} ) );

		ugmp.util.uGisUtil.setCssTextStyle( $( "#" + _self.target )[ 0 ], "overflow", "hidden !important" );

		var code = baseMapKey_.split( "_" )[ 0 ];
		var type = baseMapKey_.split( "_" )[ 1 ];

		if ( code.indexOf( "custom" ) > -1 ) {
			code = baseMapKey_;
			type = baseMapKey_;
		}

		var baseMap = _self.baseMapList[ code ][ "object" ];
		var properties = baseMap.getTypeProperties( type );

		baseMap.createBaseMap( _self.UUID, type, function(state_) {
			ugmp.uGisConfig.loading( _self.uGisMap.getDataViewId(), state_ );
		} );

		var view = _self._createView( baseMap, type );

		_self._activeChangeResolution( baseMap );

		_self._transformLayerProjection( _self.uGisMap.getMap().getView().getProjection().getCode(), properties[ "projection" ] );

		_self.uGisMap.getMap().setView( view );

		_self._setElementMargin();

		_self.uGisMap.refresh();

		_$( "#" + _self.target ).resize( function() {
			if ( _self._updateSize ) {
				_self._setElementMargin();
				_self._updateSize();
			}
		} );

		_$( window ).resize( function() {
			if ( _self._updateSize ) {
				_self._setElementMargin();
				_self._updateSize();
			}
		} );
	};


	/**
	 * uGisMap <==> uGisBaseMap 동기화 설정 사용
	 * 
	 * @param baseMap {ugmp.baseMap} 배경지도 객체
	 * 
	 * @private
	 */
	ugmp.baseMap.uGisBaseMap.prototype._activeChangeResolution = function(baseMap_) {
		var _self = this._this || this;

		var view = _self.uGisMap.getMap().getView();

		_self.uGisMap.getMap().on( "change:view", function(evt1_) {
			ol.Observable.unByKey( _self.key_changeCenter );
			ol.Observable.unByKey( _self.key_changeRotation );
			ol.Observable.unByKey( _self.key_changeResolution );

			_self.key_changeCenter = evt1_.target.getView().on( "change:center", baseMap_.syncMapCenter );
			_self.key_changeRotation = evt1_.target.getView().on( "change:rotation", baseMap_.syncMapRotation );
			_self.key_changeResolution = evt1_.target.getView().on( "change:resolution", baseMap_.syncMapZoom );
		} );
	};


	/**
	 * 배경지도를 추가한다.
	 * 
	 * {@link ugmp.baseMap.uGisBaseMapDefault ugmp.baseMap.uGisBaseMapDefault}를 확장한 배경지도 객체 또는 사용자 정의 배경지도(WMTS)를 추가할 수 있다.
	 * 
	 * 사용자 정의 배경지도(WMTS)를 추가하기 위해서는 {@link ugmp.baseMap.uGisBaseMapCustom ugmp.baseMap.uGisBaseMapCustom}를 사용한다.
	 * 
	 * 기본 내장 배경지도 코드. ["osm", "daum", "naver", "vWorld", "baroEmap", "stamen", "google"]
	 * 
	 * @param code {String} 배경지도 코드.
	 * @param obj {Object} etc -> uGisBaseMapCustom.
	 */
	ugmp.baseMap.uGisBaseMap.prototype.addBaseMapType = function(code_, obj_) {
		var _self = this._this || this;

		_self.baseMapList = _self.baseMapList || {};

		if ( obj_ && obj_.isAvailables() ) {
			_self.baseMapList[ code_ ] = {
				code : code_,
				object : obj_
			}
		}
	};


	/**
	 * View 생성
	 * 
	 * @param baseMap {String} 배경지도
	 * @param type {String} 배경지도 타입
	 * 
	 * @return nowMapView {ol.View} 현재 Map의 View
	 * 
	 * @private
	 */
	ugmp.baseMap.uGisBaseMap.prototype._createView = function(baseMap_, type_) {
		var _self = this._this || this;

		var properties = baseMap_.getTypeProperties( type_ );
		var oldView = _self.uGisMap.getMap().getView();

		var viewData = {
			projection : properties[ "projection" ],
			extent : properties[ "maxExtent" ],
			center : ol.proj.transform( oldView.getCenter(), oldView.getProjection(), properties[ "projection" ] ),
			zoom : oldView.getZoom(),
			rotation : oldView.getRotation(),
			minZoom : properties[ "minZoom" ],
			maxZoom : properties[ "maxZoom" ]
		};

		if ( type_.indexOf( "custom" ) > -1 ) {
			// delete viewData.minZoom;
			// delete viewData.maxZoom;
		}

		if ( properties[ "resolutions" ] ) {
			viewData.resolutions = properties[ "resolutions" ]
		}

		_self.nowMapView = new ol.View( viewData );

		return _self.nowMapView;
	};


	/**
	 * 피처 좌표계 변경
	 * 
	 * View가 변경 됨에 따라 좌표계가 변경 되므로 해당 좌표계에 맞게 레이어 정보 변경
	 * 
	 * @param source {String} 원본 좌표계
	 * @param destination {String} 변경 좌표계
	 * 
	 * @private
	 */
	ugmp.baseMap.uGisBaseMap.prototype._transformLayerProjection = function(source_, destination_) {
		var _self = this._this || this;

		var layers = _self.uGisMap.getMap().getLayers().getArray();
		for ( var idx_layer in layers ) {

			if ( layers[ idx_layer ] instanceof ol.layer.Group ) {
				var orderGroupLayers = layers[ idx_layer ].getLayersArray();
				for ( var i in orderGroupLayers ) {
					transform( orderGroupLayers[ i ], source_, destination_ );
				}

			} else {
				transform( layers[ idx_layer ], source_, destination_ );
			}

		}


		function transform(layer_, source_, destination_) {
			var source = layer_.getSource();
			if ( source instanceof ol.source.TileWMS || source instanceof ol.source.ImageWMS ) {
				if ( destination_ === "EPSG:4326" ) {
					// source.getParams().CRS = "EPSG:4326";
					source.getParams().VERSION = "1.1.0";
					source.updateParams( source.getParams() );
				}
			} else if ( source instanceof ol.source.Vector ) {
				/**
				 * ★ - To do : 피처 좌표변경 추가 작업 필요.
				 */

				if ( source instanceof ol.source.Cluster ) return false;

				var features = source.getFeatures();
				for ( var idx_feature in features ) {
					features[ idx_feature ].getGeometry().transform( source_, destination_ );
				}
			}
		}
	};


	/**
	 * HTML element의 크기에 맞게 변경한다.
	 * 
	 * @private
	 */
	ugmp.baseMap.uGisBaseMap.prototype._updateSize = function() {
		var _self = this._this || this;

		var code = _self.nowBaseMapKey.split( "_" )[ 0 ];
		var type = _self.nowBaseMapKey.split( "_" )[ 1 ];

		if ( code.indexOf( "custom" ) > -1 ) {
			code = _self.nowBaseMapKey;
			type = _self.nowBaseMapKey;
		}

		var baseMap = _self.baseMapList[ code ][ "object" ];

		baseMap.updateSize();
	};


	/**
	 * 해당 배경지도가 사용 가능한지 확인한다.
	 * 
	 * @param baseMapKey {String} 배경지도 키 (_로 구분).
	 * 
	 * @private
	 */
	ugmp.baseMap.uGisBaseMap.prototype._isBaseMapUsable = function(baseMapKey_) {
		var _self = this._this || this;

		var usable = true;
		var code = baseMapKey_.split( "_" )[ 0 ];
		var type = baseMapKey_.split( "_" )[ 1 ];

		if ( code.indexOf( "custom" ) > -1 ) {
			code = baseMapKey_;
			type = baseMapKey_;
		}

		if ( _self.baseMapList[ code ] ) {
			var baseMap = _self.baseMapList[ code ][ "object" ];
			var usableKeys = baseMap.getUsableKeys();
			if ( !( usableKeys.indexOf( baseMapKey_ ) !== -1 ) ) {
				usable = false;
			}

		} else {
			usable = false;
		}

		return usable;
	};


	/**
	 * 배경지도를 변경한다.
	 * 
	 * @param baseMapKey {String} 배경지도 키 (_로 구분).
	 */
	ugmp.baseMap.uGisBaseMap.prototype.changeBaseMap = function(baseMapKey_) {
		var _self = this._this || this;

		if ( baseMapKey_ === _self.nowBaseMapKey ) {
			return false;
		}

		if ( !_self._isBaseMapUsable( baseMapKey_ ) ) {
			ugmp.uGisConfig.alert_Error( baseMapKey_ + " undefined" );
			return false;
		}

		var beforeBMCode = _self.nowBaseMapKey.split( "_" )[ 0 ];
		var beforeBMType = _self.nowBaseMapKey.split( "_" )[ 1 ];
		var afterBMCode = baseMapKey_.split( "_" )[ 0 ];
		var afterBMType = baseMapKey_.split( "_" )[ 1 ];

		if ( beforeBMCode.indexOf( "custom" ) > -1 ) {
			beforeBMCode = _self.nowBaseMapKey;
			beforeBMType = _self.nowBaseMapKey;
		}
		if ( afterBMCode.indexOf( "custom" ) > -1 ) {
			afterBMCode = baseMapKey_;
			afterBMType = baseMapKey_;
		}

		var beforeBaseMap = _self.baseMapList[ beforeBMCode ][ "object" ];
		var afterBaseMap = _self.baseMapList[ afterBMCode ][ "object" ];

		var beforeProperties = beforeBaseMap.getTypeProperties( beforeBMType );
		var afterProperties = afterBaseMap.getTypeProperties( afterBMType );


		// 배경지도 코드가 같으면서 타입이 다를 때
		if ( ( beforeBMCode === afterBMCode ) && ( beforeBMType !== afterBMType ) ) {
			afterBaseMap.setMapType( afterBMType, function(state_) {
				ugmp.uGisConfig.loading( _self.uGisMap.getDataViewId(), state_ );
			} );
			var view = _self.nowMapView;

			view.setMinZoom( afterProperties.minZoom );
			view.setMaxZoom( afterProperties.maxZoom );
		} else {
			// 배경지도 코드가 다를 때
			var viewExtent = _self.nowMapView.calculateExtent( _self.uGisMap.getMap().getSize() );
			var beforeProjection = beforeProperties[ "projection" ];
			var afterProjection = afterProperties[ "projection" ];
			var beforeZoomCount = beforeProperties[ "zoomCount" ];
			var afterZoomCount = afterProperties[ "zoomCount" ];

			document.getElementById( _self.UUID ).innerHTML = "";
			document.getElementById( _self.UUID ).style.background = "";

			afterBaseMap.createBaseMap( _self.UUID, afterBMType, function(state_) {
				ugmp.uGisConfig.loading( _self.uGisMap.getDataViewId(), state_ );
			} );

			var view = _self._createView( afterBaseMap, afterBMType );

			_self._activeChangeResolution( afterBaseMap );

			if ( !( ol.proj.equivalent( ol.proj.get( beforeProjection ), ol.proj.get( afterProjection ) ) ) ) {
				_self._transformLayerProjection( beforeProjection, afterProjection );
			}

			_self.uGisMap.getMap().setView( view );

			if ( beforeBaseMap.isWorlds() ) {

				if ( !afterBaseMap.isWorlds() ) {
					// 세계 좌표계에서 변경될 때
					var afterExtent = afterProperties.maxExtent;
					afterExtent = ol.proj.transformExtent( afterExtent, afterProjection, "EPSG:3857" );
					viewExtent = ol.proj.transformExtent( viewExtent, beforeProjection, "EPSG:3857" );

					// 현재 영역이 변경되는 배경지도의 좌표계에 포함될 때
					if ( ol.extent.containsExtent( afterExtent, viewExtent ) ) {
						view.fit( ol.proj.transformExtent( viewExtent, "EPSG:3857", afterProjection ) );
						if ( afterBaseMap.isFactors() ) {
							view.setZoom( view.getZoom() + 1 );
						}
					} else {
						// 포함되지 않으면 변경되는 배경지도의 FullExtent로 설정
						view.fit( afterProperties.maxExtent );
					}
				}

			} else {
				view.fit( ol.proj.transformExtent( viewExtent, beforeProjection, afterProjection ) );

				if ( afterBaseMap.isFactors() ) {
					view.setZoom( view.getZoom() + 1 );
				}
			}

		}

		_self.setVisible( true );

		_self.uGisMap.refresh();

		_self.nowBaseMapKey = baseMapKey_;

		console.log( "####### changeBaseMap #######" );
		console.log( "baseMapType : " + _self.nowBaseMapKey );
	};


	/**
	 * 사용 가능한 배경지도 타입(키) 목록을 가져온다.
	 * 
	 * @return {Array.<String>} 배경지도 키 목록.
	 */
	ugmp.baseMap.uGisBaseMap.prototype.getUsableBaseMapList = function() {
		var _self = this._this || this;

		var usableBaseMapList = [];

		for ( var i in _self.baseMapList ) {
			usableBaseMapList = usableBaseMapList.concat( _self.baseMapList[ i ][ "object" ].getUsableKeys() );
		}

		return usableBaseMapList;
	};


	/**
	 * 배경지도를 삭제한다.
	 * 
	 * @param baseMapKey {String} 배경지도 키 (_로 구분).
	 * 
	 * @return {Array.<String>} 배경지도 키 목록.
	 */
	ugmp.baseMap.uGisBaseMap.prototype.remove = function(baseMapKey_) {
		var _self = this._this || this;

		var code = baseMapKey_.split( "_" )[ 0 ];

		// 사용자 정의 배경지도만 삭제
		if ( code.indexOf( "custom" ) > -1 ) {
			// 활성화된 배경지도 삭제 시
			if ( _self.nowBaseMapKey === baseMapKey_ ) {
				for ( var base in _self.baseMapList ) {
					if ( _self.baseMapList.hasOwnProperty( base ) ) {
						var tempBaseMap = _self.baseMapList[ base ][ "object" ].getUsableKeys()[ 0 ];
						_self.changeBaseMap( tempBaseMap );
						break;
					}
				}
			}

			delete _self.baseMapList[ baseMapKey_ ];
		}

		return _self.getUsableBaseMapList();
	};


	/**
	 * 현재 선택된 배경지도의 키를 가져온다.
	 * 
	 * @return nowBaseMapKey {String} 현재 선택된 배경지도 키.
	 */
	ugmp.baseMap.uGisBaseMap.prototype.getSelectedBaseMap = function() {
		var _self = this._this || this;
		return _self.nowBaseMapKey;
	};


	/**
	 * 배경지도의 불투명도를 가져온다.
	 * 
	 * @return opacity {Double} 배경지도 불투명도 값.
	 */
	ugmp.baseMap.uGisBaseMap.prototype.getOpacity = function(opacity_) {
		var _self = this._this || this;

		var element = document.getElementById( _self.UUID );

		return element.style.opacity;
	};


	/**
	 * 배경지도의 불투명도를 설정할 수 있다.
	 * 
	 * 0.0 ~ 1.0 사이의 숫자. 0.0 = 투명, 1.0 = 불투명
	 * 
	 * @param opacity {Double} 배경지도 불투명도 값.
	 */
	ugmp.baseMap.uGisBaseMap.prototype.setOpacity = function(opacity_) {
		var _self = this._this || this;

		var element = document.getElementById( _self.UUID );

		if ( typeof opacity_ === 'number' ) {
			element.style.opacity = opacity_;
		}
	};


	/**
	 * 배경지도의 ON/OFF 상태를 가져온다.
	 * 
	 * @return visible {Boolean} 배경지도 ON/OFF 상태.
	 */
	ugmp.baseMap.uGisBaseMap.prototype.getVisible = function() {
		var _self = this._this || this;

		var element = document.getElementById( _self.UUID );

		return ( element.style.visibility === 'visible' ) ? true : false;
	};


	/**
	 * 배경지도를 끄거나 켤 수 있다.
	 * 
	 * @param visible {Boolean} 배경지도 ON/OFF 상태.
	 */
	ugmp.baseMap.uGisBaseMap.prototype.setVisible = function(visible_) {
		var _self = this._this || this;

		var visibility = 'visible';
		var element = document.getElementById( _self.UUID );

		if ( typeof visible_ === 'boolean' ) {
			if ( !visible_ ) {
				visibility = 'hidden';
			}
		}

		element.style.visibility = visibility;
	};


	/**
	 * 배경지도의 ON/OFF 상태를 토글한다.
	 */
	ugmp.baseMap.uGisBaseMap.prototype.visibleToggle = function() {
		var _self = this._this || this;

		var element = document.getElementById( _self.UUID );
		var visibility = element.style.visibility;

		if ( visibility === 'visible' ) {
			_self.setVisible( false );
		} else {
			_self.setVisible( true );
		}
	};


	/**
	 * 현재 배경지도의 API 객체를 가져온다.
	 * 
	 * @return apiMap {Object} 현재 배경지도의 API 객체.
	 */
	ugmp.baseMap.uGisBaseMap.prototype.getApiMap = function() {
		var _self = this._this || this;
		return _self.baseMapList[ _self.nowBaseMapKey.split( "_" )[ 0 ] ][ "object" ].getApiMap();
	};


	/**
	 * 배경지도 회전 시 공백 처리를 위한 element의 여백 사이즈를 설정한다.
	 * 
	 * @private
	 */
	ugmp.baseMap.uGisBaseMap.prototype._setElementMargin = ( function() {
		var _self = this._this || this;

		if ( !_self.useElementMargin ) return false;

		var $target = $( "#" + _self.target );
		var $base = $( "#" + _self.UUID );

		var originWidth = $target.width();
		var originHeight = $target.height();
		var diagonalLength = Math.round( Math.sqrt( Math.pow( $target.width(), 2 ) + Math.pow( $target.height(), 2 ) ) );
		var interval_width = Math.round( diagonalLength - originWidth );
		var interval_height = Math.round( diagonalLength - originHeight );
		if ( interval_width % 2 === 1 ) ++interval_width;
		if ( interval_height % 2 === 1 ) ++interval_height;

		$base.css( "width", 'calc(100% + ' + interval_width + 'px)' );
		$base.css( "height", 'calc(100% + ' + interval_height + 'px)' );
		$base.css( "left", -( interval_width / 2 ) );
		$base.css( "top", -( interval_height / 2 ) );
	} );

} )();