Newer
Older
urbanLifeline_YanAn / public / static / libs / mapbox / extend / WMTSLayer.js
@zhangqy zhangqy on 3 Oct 19 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.layerId = null;

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

  WMTSLayer.prototype = {
    constructor: WMTSLayer,

    addTo: function (map, layerId) {
      this.map = map;
      this._transform = map.transform;
      this.layerId = layerId;

      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.25764139776733, 0.628820698883665, 0.251528279553466, 0.125764139776733, 0.0628820698883665, 0.0251528279553466,
      //   // 0.0125764139776733, 0.00628820698883665, 0.00251528279553466, 0.00125764139776733, 0.000628820698883665, 0.000251528279553466,
      //   // 0.000125764139776733, 0.0000628820698883665, 0.0000251528279553466, 0.0000125764139776733, 0.00000628820698883665,
      //   // 0.00000251528279553466, 0.00000125764139776733, 0.000000628820698883665, 0.000000251528279553466,
      //   // 0.7031250000000002, 0.3515625000000001, 0.17578125000000006, 0.043945312500000014, 0.021972656250000007, 0.010986328125000003,
      //   // 0.005493164062500002, 0.002746582031250001, 0.0013732910156250004, 6.866455078125002e-4, 3.433227539062501e-4,
      //   // 1.7166137695312505e-4, 8.583068847656253e-5, 4.2915344238281264e-5, 2.1457672119140632e-5, 1.0728836059570316e-5,
      //   // 5.364418029785158e-6, 2.682209014892579e-6, 1.3411045074462895e-6,

      // ][z];
      return 0.7031250000000002 / Math.pow(2, 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;
});