( function() {
"use strict";
/**
* Vector 3D 렌더링 객체.
*
* @constructor
*
* @example
*
* <pre>
* var ugRender3D = new ugmp.etc.uGisRender3D( {
* style : new ol.style.Style({...}),
* layer : new ol.layer.Vector({...}),
* initBuild : true,
* labelColumn : 'BUILD_NAME',
* heightColumn : 'BUILD_HEIGHT',
* maxResolution : 0.5
* } );
* </pre>
*
* @param opt_options {Object}
* @param opt_options.style {ol.style.Style} 스타일.
* @param opt_options.easing {ol.easing} ol.easing 타입.
* @param opt_options.layer {ol.layer.Vector} 벡터레이어 객체.
* @param opt_options.initBuild {Boolean} 초기 3D 렌더링 사용 여부.
* @param opt_options.labelColumn {String} 피처에 표시할 라벨 컬럼명.
* @param opt_options.heightColumn {String} 피처의 높이를 참조할 컬럼명.
* @param opt_options.animateDuration {Number} 3D 렌더링 지연 시간. Default is `1000`.
* @param opt_options.maxResolution {Number} 3D 렌더링 최대 Resolution. Default is `0.6`.
*
* @class
*/
ugmp.etc.uGisRender3D = ( function(opt_options) {
var _self = this;
var _super = null;
this.style = null;
this.layer = null;
this.easing = null;
this.initBuild = null;
this.labelColumn = null;
this.defaultHeight = null;
this.heightColumn = null;
this.maxResolution = null;
this.animateDuration = null;
this.res = null;
this.center = null;
this.height = null;
this.matrix = null;
this.listener = null;
this.animate = null;
this.toHeight = null;
this.buildState = null;
this.elapsedRatio = null;
/**
* Initialize
*/
( function() {
var options = opt_options || {};
_self.style = ( options.style !== undefined ) ? options.style : undefined;
_self.layer = ( options.layer !== undefined ) ? options.layer : undefined;
_self.easing = ( options.easing !== undefined ) ? options.easing : ol.easing.easeOut;
_self.initBuild = ( typeof ( options.initBuild ) === "boolean" ) ? options.initBuild : true;
_self.labelColumn = _self.labelColumn = ( options.labelColumn !== undefined ) ? options.labelColumn : "";
_self.heightColumn = _self.heightColumn = ( options.heightColumn !== undefined ) ? options.heightColumn : "";
_self.animateDuration = ( typeof ( options.animateDuration ) === "number" ) ? options.animateDuration : 1000;
_self.defaultHeight = options.defaultHeight = ( typeof ( options.defaultHeight ) === "number" ) ? options.defaultHeight : 0;
_self.maxResolution = options.maxResolution = ( typeof ( options.maxResolution ) === "number" ) ? options.maxResolution : 0.6;
_super = ol.Object.call( _self, options );
_self._init();
} )();
// END Initialize
return ugmp.util.uGisUtil.objectMerge( _super, {
_this : _self,
isBuild3D : _self.isBuild3D,
setBuild3D : _self.setBuild3D,
buildToggle : _self.buildToggle
} );
} );
ugmp.etc.uGisRender3D.prototype = Object.create( ol.Object.prototype );
ugmp.etc.uGisRender3D.prototype.constructor = ugmp.etc.uGisRender3D;
/**
* 초기화
*
* @private
*/
ugmp.etc.uGisRender3D.prototype._init = ( function() {
var _self = this._this || this;
_self._setStyle( _self.style );
_self._setLayer( _self.layer );
_self.height = _self._getHfn( _self.heightColumn );
} );
/**
* Set style associated with the renderer
*
* @param {ol.style.Style} s
*
* @private
*/
ugmp.etc.uGisRender3D.prototype._setStyle = ( function(style_) {
var _self = this._this || this;
if ( style_ instanceof ol.style.Style ) {
_self._style = style_;
} else {
_self._style = new ol.style.Style();
}
if ( !_self._style.getStroke() ) {
_self._style.setStroke( new ol.style.Stroke( {
width : 1,
color : "RED"
} ) );
}
if ( !_self._style.getFill() ) {
_self._style.setFill( new ol.style.Fill( {
color : "rgba(0,0,255,0.5)"
} ) );
}
// Get the geometry
if ( style_ && style_.getGeometry() ) {
var geom = style_.getGeometry();
if ( typeof ( geom ) === "function" ) {
_self.set( "geometry", geom );
} else {
_self.set( "geometry", function() {
return geom;
} );
}
} else {
_self.set( "geometry", function(f_) {
return f_.getGeometry();
} );
}
} );
/**
* Set layer to render 3D
*
* @private
*/
ugmp.etc.uGisRender3D.prototype._setLayer = ( function(layer_) {
var _self = this._this || this;
_self._layer = layer_;
if ( _self.listener_ ) {
_self.listener_.forEach( function(lKey_) {
ol.Observable.unByKey( lKey_ );
} );
}
_self.listener_ = layer_.on( [ "postcompose", "postrender" ], _self._onPostcompose.bind( _self ) );
_self.setBuild3D( _self.initBuild );
} );
/**
* Calculate 3D at potcompose
*
* @private
*/
ugmp.etc.uGisRender3D.prototype._onPostcompose = ( function(e_) {
var _self = this._this || this;
var res = e_.frameState.viewState.resolution;
if ( res > _self.get( "maxResolution" ) ) return;
var asd = ugMap.getMap().getRenderer().getLayerRenderer( _self.layer );
asd.declutterTree_.clear();
_self.res = res * 400;
if ( _self.animate ) {
var elapsed = e_.frameState.time - _self.animate;
if ( elapsed < _self.animateDuration ) {
_self.elapsedRatio = _self.easing( elapsed / _self.animateDuration );
// tell OL3 to continue postcompose animation
e_.frameState.animate = true;
} else {
_self.animate = false;
_self.height = _self.toHeight;
}
}
var ratio = e_.frameState.pixelRatio;
var ctx = e_.context;
var m = _self.matrix = e_.frameState.coordinateToPixelTransform;
// Old version (matrix)
if ( !m ) {
m = e_.frameState.coordinateToPixelMatrix, m[ 2 ] = m[ 4 ];
m[ 3 ] = m[ 5 ];
m[ 4 ] = m[ 12 ];
m[ 5 ] = m[ 13 ];
}
_self.center = [ ctx.canvas.width/2/ratio, ctx.canvas.height/ratio ];
var f = _self.layer.getSource().getFeaturesInExtent( e_.frameState.extent );
ctx.save();
ctx.scale( ratio, ratio );
var s = _self.style;
ctx.lineWidth = s.getStroke().getWidth();
ctx.fillStyle = ol.color.asString( s.getFill().getColor() );
ctx.strokeStyle = ol.color.asString( s.getStroke().getColor() );
var builds = [];
for ( var i = 0; i < f.length; i++ ) {
builds.push( _self._getFeature3D( f[ i ], _self._getFeatureHeight( f[ i ] ) ) );
}
_self._drawFeature3D( ctx, builds );
ctx.restore();
} );
/**
* @private
*/
ugmp.etc.uGisRender3D.prototype._getFeature3D = ( function(f_, h_) {
var _self = this._this || this;
var geom = _self.get( "geometry" )( f_ );
var c = geom.getCoordinates();
switch ( geom.getType() ) {
case "Polygon" :
c = [ c ];
// fallthrough
case "MultiPolygon" :
var build = [];
for ( var i = 0; i < c.length; i++ ) {
for ( var j = 0; j < c[ i ].length; j++ ) {
var b = [];
for ( var k = 0; k < c[ i ][ j ].length; k++ ) {
b.push( _self._hvector( c[ i ][ j ][ k ], h_ ) );
}
build.push( b );
}
}
return {
type : "MultiPolygon",
feature : f_,
geom : build
};
case "Point" :
return {
type : "Point",
feature : f_,
geom : _self._hvector( c, h )
};
default :
return {};
}
} );
/**
* Create a function that return height of a feature
*
* @param {function|string|number} h a height function or a popertie name or a fixed value
*
* @private
*
* @return {function} function(f) return height of the feature f
*/
ugmp.etc.uGisRender3D.prototype._getHfn = ( function(h_) {
var _self = this._this || this;
switch ( typeof ( h_ ) ) {
case 'function' :
return h_;
case 'string' : {
var dh = _self.get( "defaultHeight" );
return ( function(f_) {
return ( Number( f_.get( h_ ) ) || dh );
} );
}
case 'number' :
return ( function(/* f */) {
return h_;
} );
default :
return ( function(/* f */) {
return 10;
} );
}
} );
/**
* @private
*/
ugmp.etc.uGisRender3D.prototype._hvector = ( function(pt_, h_) {
var _self = this._this || this;
var p0 = [ pt_[ 0 ] * _self.matrix[ 0 ] + pt_[ 1 ] * _self.matrix[ 1 ] + _self.matrix[ 4 ],
pt_[ 0 ] * _self.matrix[ 2 ] + pt_[ 1 ] * _self.matrix[ 3 ] + _self.matrix[ 5 ] ];
return {
p0 : p0,
p1 : [ p0[ 0 ] + h_ / _self.res * ( p0[ 0 ] - _self.center[ 0 ] ), p0[ 1 ] + h_ / _self.res * ( p0[ 1 ] - _self.center[ 1 ] ) ]
};
} );
/**
* @private
*/
ugmp.etc.uGisRender3D.prototype._getFeatureHeight = ( function(f_) {
var _self = this._this || this;
if ( _self.animate ) {
var h1 = _self.height( f_ );
var h2 = _self.toHeight( f_ );
return ( h1 * ( 1 - _self.elapsedRatio ) + _self.elapsedRatio * h2 );
} else {
return _self.height( f_ );
}
} );
/**
* @private
*/
ugmp.etc.uGisRender3D.prototype._drawFeature3D = ( function(ctx_, build_) {
var _self = this._this || this;
var i, j, b, k;
// Construct
for ( i = 0; i < build_.length; i++ ) {
switch ( build_[ i ].type ) {
case "MultiPolygon" : {
for ( j = 0; j < build_[ i ].geom.length; j++ ) {
b = build_[ i ].geom[ j ];
for ( k = 0; k < b.length; k++ ) {
ctx_.beginPath();
ctx_.moveTo( b[ k ].p0[ 0 ], b[ k ].p0[ 1 ] );
ctx_.lineTo( b[ k ].p1[ 0 ], b[ k ].p1[ 1 ] );
ctx_.stroke();
}
}
break;
}
case "Point" : {
var g = build_[ i ].geom;
ctx_.beginPath();
ctx_.moveTo( g.p0[ 0 ], g.p0[ 1 ] );
ctx_.lineTo( g.p1[ 0 ], g.p1[ 1 ] );
ctx_.stroke();
break;
}
default :
break;
}
}
// Roof
for ( i = 0; i < build_.length; i++ ) {
switch ( build_[ i ].type ) {
case "MultiPolygon" : {
ctx_.beginPath();
for ( j = 0; j < build_[ i ].geom.length; j++ ) {
b = build_[ i ].geom[ j ];
if ( j == 0 ) {
ctx_.moveTo( b[ 0 ].p1[ 0 ], b[ 0 ].p1[ 1 ] );
for ( k = 1; k < b.length; k++ ) {
ctx_.lineTo( b[ k ].p1[ 0 ], b[ k ].p1[ 1 ] );
}
} else {
ctx_.moveTo( b[ 0 ].p1[ 0 ], b[ 0 ].p1[ 1 ] );
for ( k = b.length - 2; k >= 0; k-- ) {
ctx_.lineTo( b[ k ].p1[ 0 ], b[ k ].p1[ 1 ] );
}
}
ctx_.closePath();
}
ctx_.fill( "evenodd" );
ctx_.stroke();
b = build_[ i ];
var text = b.feature.get( _self.labelColumn );
if ( text ) {
var center = ugmp.util.uGisGeoSpatialUtil.getGeomCenter( b.feature.getGeometry() );
var p = _self._hvector( center, _self._getFeatureHeight( b.feature ) ).p1;
var f = ctx_.fillStyle;
var m = ctx_.measureText( text );
var h = Number( ctx_.font.match( /\d+(\.\d+)?/g ).join( [] ) );
ctx_.fillStyle = "rgba(255,255,255,0.5)";
ctx_.fillRect( p[ 0 ] - m.width / 2 - 5, p[ 1 ] - h - 5, m.width + 10, h + 10 )
ctx_.strokeRect( p[ 0 ] - m.width / 2 - 5, p[ 1 ] - h - 5, m.width + 10, h + 10 )
ctx_.font = "bold 12px Verdana";
ctx_.fillStyle = 'black';
ctx_.textAlign = 'center';
ctx_.textBaseline = 'bottom';
ctx_.fillText( text, p[ 0 ], p[ 1 ] );
ctx_.fillStyle = f;
}
break;
}
case "Point" : {
b = build_[ i ];
var text = b.feature.get( _self.labelColumn );
if ( text ) {
var p = b.geom.p1;
var f = ctx_.fillStyle;
ctx_.fillStyle = ctx_.strokeStyle;
ctx_.textAlign = 'center';
ctx_.textBaseline = 'bottom';
ctx_.fillText( text, p[ 0 ], p[ 1 ] );
var m = ctx_.measureText( text );
var h = Number( ctx_.font.match( /\d+(\.\d+)?/g ).join( [] ) );
ctx_.fillStyle = "rgba(255,255,255,0.5)";
ctx_.fillRect( p[ 0 ] - m.width / 2 - 5, p[ 1 ] - h - 5, m.width + 10, h + 10 )
ctx_.strokeRect( p[ 0 ] - m.width / 2 - 5, p[ 1 ] - h - 5, m.width + 10, h + 10 )
ctx_.fillStyle = f;
}
break;
}
default :
break;
}
}
} );
/**
* Check if animation is on
*
* @private
*
* @return {Boolean} 현재 animation 상태.
*/
ugmp.etc.uGisRender3D.prototype._animating = ( function() {
var _self = this._this || this;
if ( _self.animate && new Date().getTime() - _self.animate > _self.animateDuration ) {
_self.animate = false;
}
return !!_self.animate;
} );
/**
* 3D 렌더링 ON/OFF 설정을 한다.
*
* @param state {Boolean} 사용 설정 값.
*/
ugmp.etc.uGisRender3D.prototype.setBuild3D = ( function(state_) {
var _self = this._this || this;
if ( state_ ) {
_self.buildState = true;
_self.toHeight = _self._getHfn( _self.heightColumn );
} else {
_self.buildState = false;
_self.toHeight = _self._getHfn( 0 );
}
_self.animate = new Date().getTime();
// Force redraw
_self.layer.changed();
} );
/**
* 3D 렌더링 ON/OFF 상태를 토글한다.
*/
ugmp.etc.uGisRender3D.prototype.buildToggle = ( function() {
var _self = this._this || this;
_self.setBuild3D( !_self.buildState );
} );
/**
* 3D 렌더링 ON/OFF 상태를 가져온다.
*
* @return {Boolean} 현재 렌더링 ON/OFF 상태.
*/
ugmp.etc.uGisRender3D.prototype.isBuild3D = ( function() {
var _self = this._this || this;
_self.setBuild3D( !_self.buildState );
} );
} )();