Newer
Older
DH_Apicture / public / static / libs / mapbox / extend / WMTSLayer.js
@zhangqy zhangqy 29 days ago 15 KB first commit
(function (global, factory) {
	typeof exports === 'object' && typeof module !== 'undefined' ?
		module.exports = factory() :
		typeof define === 'function' && define.amd ? define(factory) :
			(global = typeof globalThis !== 'undefined' ? globalThis :
				global || self, global.mapboxgl1.WMTSLayer = factory(global));
}(window,(function(global){
	function clamp(value,min,max){
		return value < min ? min : value > max ? max: value
	}

	var caches = {
		data:{},
		get:function(key){
			return this.data[key];
		},
		put:function(key,value){
			this.data[key] = value;
		},
		clear:function(){
			this.data = {};
		}
	};

	var lib = mapboxgl1;

	var transparentPngUrl = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAAC0lEQVQYV2NgAAIAAAUAAarVyFEAAAAASUVORK5CYII=';

	var transparentImage = (function(){
		var canvas = document.createElement("canvas");
		canvas.width = 256;
		canvas.height = 256;
		var context = canvas.getContext("2d");
		context.fillStyle = "rgba(0,0,0,0)";
		context.fillRect(0,0,256,256);
		return canvas;
	})();

	var vetexShaderSource = `
		uniform mat4 u_Matrix;
		uniform vec4 u_Translate;
		attribute vec3 a_Position;
		attribute vec2 a_UV;
		varying vec2 v_UV;
		void main(){
			v_UV = a_UV;
			gl_Position = u_Matrix * vec4( (a_Position.xy + u_Translate.xy), 0.0 ,1.0 );
		}
	`;

	var fragmentShaderSource = `
		#ifdef GL_ES
			precision mediump float;
		#endif
		varying vec2 v_UV;
		uniform sampler2D texture;
		void main(){
			vec4 textureColor = texture2D(texture,v_UV);
			gl_FragColor = textureColor;
		}
	`;

	function WMTSLayer(options){

		this._options = Object.assign({
			minzoom:3,
			maxzoom:22,
			tileSize:256
		},options);

		this._extent = this._options.extent || [-180,-90,180,90];

		this.map = null;
		this._transform = null;
		this._program = null;
		this._gl = null;

		//当前可视区域的切片
		this._tiles = {};

	}

	WMTSLayer.prototype = {

		constructor:WMTSLayer,

		addTo:function(map){

			this.map = map;
			this._transform = map.transform;
			this._layerId = "vectorTileLayer_"+Date.now();

			map.addLayer({
				id:this._layerId,
				type: 'custom',
				onAdd: (function(_this){
					return function(map,gl){
						return _this._onAdd(map,gl,this);
					}
				})(this),
				render: (function(_this){
					return function(gl, matrix){
						return _this._render(gl, matrix, this);
					}
				})(this)
			});

			map.on("remove",function(){
				caches.clear();
			});
		},

		_onAdd: function(map,gl){
			var _this = this;

			this._gl = gl;

			this.transparentTexture = gl.createTexture();
			gl.bindTexture(gl.TEXTURE_2D, this.transparentTexture);
			gl.texParameteri(gl.TEXTURE_2D,gl.TEXTURE_MIN_FILTER,gl.LINEAR);
			gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, transparentImage);
			gl.bindTexture(gl.TEXTURE_2D,null);

			var vetexShader = gl.createShader(gl.VERTEX_SHADER)
			gl.shaderSource(vetexShader,vetexShaderSource);
			gl.compileShader(vetexShader);

			if (!gl.getShaderParameter(vetexShader,gl.COMPILE_STATUS)) {
				throw "Shader Compile Error: " + (gl.getShaderInfoLog(vetexShader));
			}

			var fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
			gl.shaderSource(fragmentShader,fragmentShaderSource);
			gl.compileShader(fragmentShader);

			if (!gl.getShaderParameter(fragmentShader,gl.COMPILE_STATUS)) {
				throw "Shader Compile Error: " + (gl.getShaderInfoLog(fragmentShader));
			}

			var program = this._program = gl.createProgram();
			gl.attachShader(program, vetexShader);
			gl.attachShader(program, fragmentShader);
			gl.linkProgram(program);
			/**
			 * 属性
			 */
			var attributes = this._attributes = {
				aPosition:{
					name:"a_Position",
					location:gl.getAttribLocation(this._program,"a_Position"),
				},
				aUV:{
					name:"a_UV",
					location:gl.getAttribLocation(this._program,"a_UV"),
				}
			};

			/**
			 * 缓冲区
			 */
			this._buffers = {
				aPositionBuffer:{
					buffer:gl.createBuffer(),
					size:3,
					attribute: attributes["aPosition"],
					points: new Float32Array(3 * 6),
					update:function(extent){
					},
					update1:function(extent){
						gl.bindBuffer(gl.ARRAY_BUFFER,this.buffer);
						var centerMecatorExtent = extent;
						var minx = centerMecatorExtent[0],
							miny = centerMecatorExtent[1],
							maxx = centerMecatorExtent[2],
							maxy = centerMecatorExtent[3];
						var points = this.points;
						points[0] = minx ,points[1] = maxy, points[2] = 0.0 ,
							points[3] = maxx ,points[4] = maxy, points[5] = 0.0,
							points[6] = minx ,points[7] = miny, points[8] = 0.0  ,
							points[9] = maxx ,points[10] = maxy, points[11] = 0.0 ,
							points[12] = minx,points[13] = miny, points[14] = 0.0,
							points[15] = maxx,points[16] = miny, points[17] = 0.0 ;
						gl.bufferData(gl.ARRAY_BUFFER,points, gl.STATIC_DRAW);
						gl.enableVertexAttribArray(this.attribute.location);
						gl.vertexAttribPointer(this.attribute.location,this.size,gl.FLOAT,false,0,0);
					}
				},
				aUVBuffer:{
					buffer:gl.createBuffer(),
					size:2,
					attribute:attributes["aUV"],
					points:new Float32Array( [0,0,1,0,0,1,1,0,0,1,1,1] ),
					hasBufferData:false,
					update:function(){
						gl.bindBuffer(gl.ARRAY_BUFFER,this.buffer);
						if(!this.hasBufferData){
							gl.bufferData(gl.ARRAY_BUFFER, this.points, gl.STATIC_DRAW);
							this.hasBufferData = true;
						}
						gl.enableVertexAttribArray(this.attribute.location);
						gl.vertexAttribPointer(this.attribute.location,this.size,gl.FLOAT,false,0,0);
					}
				}
			}
			/**
			 * 变量
			 */
			this._uniforms = {
				uMatrix:{
					value:null,
					location:gl.getUniformLocation(this._program,"u_Matrix"),
					update:function(matrix){
						if(this.value !== matrix){
							gl.uniformMatrix4fv(this.location,false,matrix);
						}
					}
				},
				uTranslate:{
					value:[0,0],
					location:gl.getUniformLocation(this._program,"u_Translate"),
					update:function(){}
				},
				uTexture:{
					value:null,
					location:gl.getUniformLocation(this._program, 'texture'),
					update:function(){}
				}
			};
		},
		/**
		 * 渲染
		 * @param {*} gl
		 * @param {*} matrix
		 */
		_render:function(gl, matrix){
			if(this._program){

				var transform = this._transform;
				var options = this._options;
				var tileSize = options.tileSize ||256;

				var z  =  transform.coveringZoomLevel({
					tileSize:tileSize,
					roundZoom:true
				});

				this.realz = z;

				z = z < 5 ? 5 : z;

				this.z = z;

				if (options.minzoom !== undefined && z < options.minzoom) { z = 0; }

				if (options.maxzoom !== undefined && z > options.maxzoom) { z = options.maxzoom; }

				var resolution =  this.resolution = this.getResolutionFromZ(z);

				var center = transform.center;

				//var centerCoord = lib.MercatorCoordinate.fromLngLat(transform.center);
				var maxx = clamp (center.lng + resolution * tileSize, -180, 180);
				var miny = clamp (center.lat - resolution * tileSize, -90, 90);
				var minx = clamp (center.lng, -180, 180) ,maxy = clamp(center.lat, -90,90) ;
				var leftBottom = lib.MercatorCoordinate.fromLngLat([minx,miny]);
				var topRight = lib.MercatorCoordinate.fromLngLat([maxx,maxy]);

				this.centerMecatorExtent = [leftBottom.x,leftBottom.y,topRight.x,topRight.y];

				gl.useProgram(this._program);

				gl.enable(gl.BLEND);

				gl.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE_MINUS_SRC_ALPHA);


				for(let key in this._uniforms){
					this._uniforms[key].update(matrix);
				}

				for(let key in this._buffers){
					this._buffers[key].update();
				}

				this.calcTilesInView();

				this.renderTiles();

			}
		},
		renderTiles(){
			var gl = this._gl;
			var tiles = this._tiles;
			var tile;

			for(var key in tiles){

				tile = tiles[key];

				tile.calcExtent();

				this._buffers.aPositionBuffer.update1(tile.extent);

				gl.uniform4fv(this._uniforms.uTranslate.location,tile.translate);
				gl.activeTexture(gl.TEXTURE0);
				if(tile.texture){
					gl.bindTexture(gl.TEXTURE_2D, tile.texture);
				}else{
					gl.bindTexture(gl.TEXTURE_2D, this.transparentTexture);
				}
				gl.uniform1i(this._uniforms.uTexture.location, 0);
				gl.drawArrays(gl.TRIANGLES, 0, 6);
			}

		},
		/**
		 * 计算当前可视范围内的切片
		 */
		calcTilesInView:function(){
			var z = this.z;
			var options = this._options;
			var tileSize = options.tileSize ||256;

			var resolution = this.resolution;

			var extent = this._extent;
			var tileRes = resolution * tileSize;
			var viewExtent = this.getViewExtent();

			var startX =  Math.floor((viewExtent[0] - extent[0]) / tileRes);
			var startY =  Math.floor((extent[3] - viewExtent[3]) / tileRes);
			var endX   =  Math.ceil((viewExtent[2] - extent[0]) / tileRes);
			var endY   =  Math.ceil((extent[3] - viewExtent[1]) / tileRes);

			// startX = startX < 20 ? 20 : startX;
			startY = startY < 1 ?  1 : startY;
			// endX = endX < 31 ? 31 : endX;
			//endY = endY < 20 ? 20 : endY;
			if(this.realz < 5){
				endY = endY > 10 ? 10 : endY
			}


			var i,j,key,tile;

			var tiles = this._tiles;

			var newTiles = {}

			for(i = startX ; i <  endX; i ++){
				for(j = startY; j < endY ; j ++){
					key = this._key(z,i,j);
					if(!tiles[key]){
						caches.get(key);
						if(caches.get(key)){
							newTiles[key] = caches.get(key);
						}else{
							tile = new Tile(z,i,j,resolution,this);
							newTiles[key] = tile;
						}
					}else{
						newTiles[key] = tiles[key];
						delete tiles[key];
					}
				}
			};

			for(var key in tiles){
				if(tiles[key].request){
					tiles[key].request.cancel();
				}
			}

			this._tiles = newTiles;

		},
		_key:function(z,x,y){
			return z+'/'+x+"/"+y;
		},
		/**
		 * 计算分辨率
		 */
		getResolutionFromZ:function(z){
			return 1.4062500000000002 / Math.pow(2,z);
		},
		/**
		 * 计算extent
		 */
		getViewExtent:function(){
			var transform = this._transform;
			var bounds = [
				transform.pointLocation(new lib.Point(0, 0)),
				transform.pointLocation(new lib.Point(transform.width, 0)),
				transform.pointLocation(new lib.Point(transform.width, transform.height)),
				transform.pointLocation(new lib.Point(0, transform.height))
			];

			var minx , miny , maxx, maxy;

			for(var i = 0,point = null ; i < bounds.length ; i ++ ){
				point = bounds[i];
				if(i ==  0 ){
					minx = point.lng;
					miny = point.lat;
					maxx = point.lng;i
					maxy = point.lat;
				}else {
					if(minx > point.lng) minx = point.lng;
					if(miny > point.lat) miny = point.lat;
					if(maxx < point.lng) maxx = point.lng;
					if(maxy < point.lat) maxy = point.lat;
				}
			}

			return [
				clamp(minx,-180,180),
				clamp(miny,-90,90),
				clamp(maxx,-180,180),
				clamp(maxy,-90,90)
			]
		},
		/**
		 * 重绘
		 */
		repaint:function(){
			this.map.triggerRepaint();
		},
		hide: function(){
			this.map.setLayoutProperty(this._layerId, 'visibility', 'none');
		},
		show: function(){
			this.map.setLayoutProperty(this._layerId, 'visibility', 'visible');
		}
	}

	/**
	 * 请求
	 * @param {*} url
	 * @param {*} callback
	 * @param {*} async
	 */
	var getImage = (function(){

		var MAX_REQUEST_NUM = 16;

		var requestNum = 0;
		var requestQuenes = [];

		function getImage(url,callback){
			if(requestNum > MAX_REQUEST_NUM){
				var quene = {
					url:url,
					callback:callback,
					canceled:false,
					cancel:function(){
						this.canceled = true;
					}
				}
				requestQuenes.push(quene);
				return quene;
			}

			var advanced = false;
			var advanceImageRequestQueue = function () {
				if (advanced) { return; }
				advanced = true;
				requestNum--;
				while (requestQuenes.length && requestNum < MAX_REQUEST_NUM) { // eslint-disable-line
					var request = requestQuenes.shift();
					var url = request.url;
					var callback = request.callback;
					var canceled = request.canceled;
					if (!canceled) {
						request.cancel = getImage(url, callback).cancel;
					}
				}
			};

			requestNum ++ ;
			var req = get(url,function(error,data){
				advanceImageRequestQueue();
				if(!error){
					var URL = window.URL || window.webkitURL;
					var blob = new Blob([data],{type:"image/png"});
					var blobUrl = URL.createObjectURL(blob)
					var image = new Image();
					image.src = blobUrl;
					image.onload = function(){
						callback(image);
						URL.revokeObjectURL(image.src);
					};
					image.src = data.byteLength ? URL.createObjectURL(blob) : transparentPngUrl;
				}

			});

			return {
				cancel:function(){
					req.abort();
				}
			}
		}

		function get(url, callback, async) {
			var xhr = new XMLHttpRequest();
			xhr.open('GET', url, async === false ? false : true);
			xhr.responseType = "arraybuffer";
			xhr.onabort = function (event) {
				callback(true, null);
			};
			xhr.onload = function (event) {
				if (!xhr.status || xhr.status >= 200 && xhr.status < 300) {
					var source;
					source = xhr.response;
					if (source) {
						try {
							source = eval("(" + source + ")");
						} catch (e) {
						}
					}
					if (source) {
						callback(false, source);
					} else {
						callback(false, null);
					}
				}
			};
			xhr.onerror = function (e) {
				callback(true, null);
			};
			xhr.send(null);
			return xhr;
		}

		return getImage;
	})()



	function Tile(z,x,y,resolution,layer){
		this._resolution = resolution;
		this._layer = layer;
		this._coord = [z,x,y];
		this._gl = layer._gl;
		this._url = layer._options.url;
		this.texture = null;
		this.loaded = false;
		this.tileSize = layer._options.tileSize;
		this.worldExtent = layer._extent;
		this.subdomains = layer._options.subdomains || [];
		this.extent = [0,0,0,0];
		this.translate = [0,0,0,0];
		this._load();
	}

	Tile.prototype = {
		constructor:Tile,

		calcExtent:function(){

			var gl = this._gl;
			var worldExtent = this.worldExtent;
			var tileSize = this.tileSize;
			var resolution = this._resolution;
			var coord = this._coord;
			var x = coord[1],y = coord[2];

			var maxTileNum = Math.ceil((worldExtent[3] - worldExtent[1]) / resolution / tileSize);

			var minx = clamp(x * tileSize * resolution - worldExtent[2],-180,180);
			var maxx = clamp(minx + tileSize * resolution, -180,180);
			var maxy = clamp(worldExtent[3] - y * tileSize * resolution, -90 , 90 );
			var miny = clamp(maxy - tileSize * resolution, -90,90);

			var y1 = y + 1;
			y1 = y1 > maxTileNum ? maxTileNum : y1;
			maxy1 = worldExtent[3] - y1 * tileSize * resolution;


			var bl = lib.MercatorCoordinate.fromLngLat([minx,miny]);
			var tr = lib.MercatorCoordinate.fromLngLat([maxx,maxy]);

			this.extent[0] = bl.x;
			this.extent[1] = bl.y;
			this.extent[2] = tr.x;
			this.extent[3] = tr.y;

			//var centerMecatorExtent = this._layer.centerMecatorExtent;

			// if(!this.translate){
			// 	this.translate = [0,0,0,0];
			// }

			// this.translate[0] = bl.x - centerMecatorExtent[0];
			// this.translate[1] = bl.y - centerMecatorExtent[1];
			// this.translate[2] = tr.x - centerMecatorExtent[2];
			// this.translate[3] = tr.y - centerMecatorExtent[3];

		},
		_load: function(){
			var gl = this._gl
			var _this = this;
			var z = this._coord[0];
			var x = this._coord[1];
			var y = this._coord[2];
			var url = this._url.replace("{s}",this.subdomains[Math.floor(Math.random() * this.subdomains.length)]).replace("{x}",x).replace("{y}",y).replace("{z}",z);

			this.request = getImage(url,function(img){
				delete _this .request;
				if(_this._gl){
					var texture = _this.texture = gl.createTexture();
					gl.bindTexture(gl.TEXTURE_2D, texture);
					gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
					gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
					gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, true);
					gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
					gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
					gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
					gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
					gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, img);
					gl.bindTexture(gl.TEXTURE_2D, null);
					caches.put(z+"/"+x+"/"+y,_this);
					_this.loaded = true;
					_this._layer.repaint();
				}
			});
		}
	}

	return WMTSLayer;
})));