前言
leaflet 入门开发系列环境知识点了解:
- leaflet api文档介绍,详细介绍 leaflet 每个类的函数以及属性等等
- leaflet 在线例子
- leaflet 插件,leaflet 的插件库,非常有用
内容概览
leaflet拓展wms以及wmts地图范围裁剪功能
源代码demo下载
效果图如下:
具体实现参考leaflet裁剪插件:https://github.com/aparshin/leaflet-boundary-canvas
- 自定义wms以及wmts类的部分核心代码,裁剪部分参照leaflet-boundary-canvas插件核心裁剪部分源码,完整的见源码demo下载
var isRingBbox = function (ring, bbox) { if (ring.length !== 4) { return false; } var p, sumX = 0, sumY = 0; for (p = 0; p < 4; p++) { if ((ring[p].x !== bbox.min.x && ring[p].x !== bbox.max.x) || (ring[p].y !== bbox.min.y && ring[p].y !== bbox.max.y)) { return false; } sumX += ring[p].x; sumY += ring[p].y; //bins[Number(ring[p].x === bbox.min.x) + 2 * Number(ring[p].y === bbox.min.y)] = 1; } //check that we have all 4 vertex of bbox in our geometry return sumX === 2*(bbox.min.x + bbox.max.x) && sumY === 2*(bbox.min.y + bbox.max.y); }; var ExtendMethods = { _toMercGeometry: function(b, isGeoJSON) { var res = []; var c, r, p, mercComponent, mercRing, coords; if (!isGeoJSON) { if (!(b[0] instanceof Array)) { b = [[b]]; } else if (!(b[0][0] instanceof Array)) { b = [b]; } } for (c = 0; c < b.length; c++) { mercComponent = []; for (r = 0; r < b[c].length; r++) { mercRing = []; for (p = 0; p < b[c][r].length; p++) { coords = isGeoJSON ? L.latLng(b[c][r][p][1], b[c][r][p][0]) : b[c][r][p]; mercRing.push(this._map.project(coords, 0)); } mercComponent.push(mercRing); } res.push(mercComponent); } return res; }, //lazy calculation of layer's boundary in map's projection. Bounding box is also calculated _getOriginalMercBoundary: function () { if (this._mercBoundary) { return this._mercBoundary; } var compomentBbox, c; if (L.Util.isArray(this.options.boundary)) { //Depricated: just array of coordinates this._mercBoundary = this._toMercGeometry(this.options.boundary); } else { //GeoJSON this._mercBoundary = []; var processGeoJSONObject = function(obj) { if (obj.type === 'GeometryCollection') { obj.geometries.forEach(processGeoJSONObject); } else if (obj.type === 'Feature') { processGeoJSONObject(obj.geometry); } else if (obj.type === 'FeatureCollection') { obj.features.forEach(processGeoJSONObject); } else if (obj.type === 'Polygon') { this._mercBoundary = this._mercBoundary.concat(this._toMercGeometry([obj.coordinates], true)); } else if (obj.type === 'MultiPolygon') { this._mercBoundary = this._mercBoundary.concat(this._toMercGeometry(obj.coordinates, true)); } }.bind(this); processGeoJSONObject(this.options.boundary); } this._mercBbox = new L.Bounds(); for (c = 0; c < this._mercBoundary.length; c++) { compomentBbox = new L.Bounds(this._mercBoundary[c][0]); this._mercBbox.extend(compomentBbox.min); this._mercBbox.extend(compomentBbox.max); } return this._mercBoundary; }, _getClippedGeometry: function(geom, bounds) { var clippedGeom = [], clippedComponent, clippedExternalRing, clippedHoleRing, iC, iR; for (iC = 0; iC < geom.length; iC++) { clippedComponent = []; clippedExternalRing = L.PolyUtil.clipPolygon(geom[iC][0], bounds); if (clippedExternalRing.length === 0) { continue; } clippedComponent.push(clippedExternalRing); for (iR = 1; iR < geom[iC].length; iR++) { clippedHoleRing = L.PolyUtil.clipPolygon(geom[iC][iR], bounds); if (clippedHoleRing.length > 0) { clippedComponent.push(clippedHoleRing); } } clippedGeom.push(clippedComponent); } if (clippedGeom.length === 0) { //we are outside of all multipolygon components return {isOut: true}; } for (iC = 0; iC < clippedGeom.length; iC++) { if (isRingBbox(clippedGeom[iC][0], bounds)) { //inside exterior rings and no holes if (clippedGeom[iC].length === 1) { return {isIn: true}; } } else { //intersects exterior ring return {geometry: clippedGeom}; } for (iR = 1; iR < clippedGeom[iC].length; iR++) { //inside exterior ring, but have intersection with a hole if (!isRingBbox(clippedGeom[iC][iR], bounds)) { return {geometry: clippedGeom}; } } } //we are inside all holes in geometry return {isOut: true}; }, // Calculates intersection of original boundary geometry and tile boundary. // Uses quadtree as cache to speed-up intersection. // Return // {isOut: true} if no intersection, // {isIn: true} if tile is fully inside layer's boundary // {geometry: <LatLng[][][]>} otherwise _getTileGeometry: function (x, y, z, skipIntersectionCheck) { if ( !this.options.boundary) { return {isIn: true}; } var cacheID = x + ":" + y + ":" + z, zCoeff = Math.pow(2, z), parentState, cache = this._boundaryCache; if (cache[cacheID]) { return cache[cacheID]; } var mercBoundary = this._getOriginalMercBoundary(), ts = this.options.tileSize, tileBbox = new L.Bounds(new L.Point(x * ts / zCoeff, y * ts / zCoeff), new L.Point((x + 1) * ts / zCoeff, (y + 1) * ts / zCoeff)); //fast check intersection if (!skipIntersectionCheck && !tileBbox.intersects(this._mercBbox)) { return {isOut: true}; } if (z === 0) { cache[cacheID] = {geometry: mercBoundary}; return cache[cacheID]; } parentState = this._getTileGeometry(Math.floor(x / 2), Math.floor(y / 2), z - 1, true); if (parentState.isOut || parentState.isIn) { return parentState; } cache[cacheID] = this._getClippedGeometry(parentState.geometry, tileBbox); return cache[cacheID]; }, _drawTileInternal: function (canvas, tilePoint, url, callback) { var zoom = this._getZoomForUrl(), state = this._getTileGeometry(tilePoint.x, tilePoint.y, zoom); if (state.isOut) { callback(); return; } var ts = this.options.tileSize, tileX = ts * tilePoint.x, tileY = ts * tilePoint.y, zCoeff = Math.pow(2, zoom), ctx = canvas.getContext('2d'), imageObj = new Image(), _this = this; var setPattern = function () { var c, r, p, pattern, geom; if (!state.isIn) { geom = state.geometry; //console.log('geom',geom); ctx.beginPath(); for (c = 0; c < geom.length; c++) { for (r = 0; r < geom[c].length; r++) { if (geom[c][r].length === 0) { continue; } ctx.moveTo(geom[c][r][0].x * zCoeff - tileX, geom[c][r][0].y * zCoeff - tileY); for (p = 1; p < geom[c][r].length; p++) { ctx.lineTo(geom[c][r][p].x * zCoeff - tileX, geom[c][r][p].y * zCoeff - tileY); } } } ctx.clip(); } pattern = ctx.createPattern(imageObj, "repeat"); ctx.beginPath(); ctx.rect(0, 0, canvas.width, canvas.height); ctx.fillStyle = pattern; ctx.fill(); callback(); }; if (this.options.crossOrigin) { imageObj.crossOrigin = ''; } imageObj.onload = function () { //TODO: implement correct image loading cancelation canvas.complete = true; //HACK: emulate HTMLImageElement property to make happy L.TileLayer setTimeout(setPattern, 0); //IE9 bug - black tiles appear randomly if call setPattern() without timeout } imageObj.src = url; //console.log('imageObj.src',imageObj.src); }, }; L.TileLayer.GISHOMEWMS = L.TileLayer.WMS.extend({ defaultWmsParams: { service: 'WMS', request: 'GetMap', layers: '', styles: '', format: 'image/jpeg', transparent: false, version: '1.1.1', boundary: null }, options: { crs: null, uppercase: false }, includes: ExtendMethods, initialize: function(url, options) { this._url = url; var wmsParams = L.extend({}, this.defaultWmsParams); // all keys that are not TileLayer options go to WMS params for (var i in options) { if (!(i in this.options)) { wmsParams[i] = options[i]; } } // options = setOptions(this, options); var realRetina = options.detectRetina && L.Browser.retina ? 2 : 1; var tileSize = this.getTileSize(); wmsParams.width = tileSize.x * realRetina; wmsParams.height = tileSize.y * realRetina; this.wmsParams = wmsParams; //编辑 L.TileLayer.prototype.initialize.call(this, url, options); this._boundaryCache = {}; //cache index "x:y:z" this._mercBoundary = null; this._mercBbox = null; options = L.setOptions(this, options); }, onAdd: function (map) { this._crs = this.options.crs || map.options.crs; this._wmsVersion = parseFloat(this.wmsParams.version); var projectionKey = this._wmsVersion >= 1.3 ? 'crs' : 'srs'; this.wmsParams[projectionKey] = this._crs.code; //L.TileLayer.prototype.onAdd.call(this, map); (L.TileLayer.Canvas || L.TileLayer).prototype.onAdd.call(this, map); }, //编辑 createTile: function(coords, done){ var tile = document.createElement('canvas'), url = this.getTileUrl(coords); tile.width = tile.height = this.options.tileSize; this._drawTileInternal(tile, coords, url, L.bind(done, null, null, tile)); return tile; } }); L.tileLayer.gishomewms = function(url, options) { return new L.TileLayer.GISHOMEWMS(url, options); }; L.TileLayer.WMTS = L.TileLayer.extend({ defaultWmtsParams: { service: 'WMTS', request: 'GetTile', version: '1.0.0', layers: '', styles: '', tilematrixSet: '', // format: 'image/jpeg', format: 'image/png', boundary: null }, includes: ExtendMethods, initialize: function(url, options) { this._url = url; var wmtsParams = L.extend({}, this.defaultWmtsParams); var tileSize = options.tileSize || this.options.tileSize; if (options.detectRetina && L.Browser.retina) { wmtsParams.width = wmtsParams.height = tileSize * 2; } else { wmtsParams.width = wmtsParams.height = tileSize; } for (var i in options) { if (!this.options.hasOwnProperty(i) && i !== "matrixIds") { wmtsParams[i] = options[i]; } } this.wmtsParams = wmtsParams; this.matrixIds = options.matrixIds || this.getDefaultMatrix(); //编辑 L.TileLayer.prototype.initialize.call(this, url, options); this._boundaryCache = {}; //cache index "x:y:z" this._mercBoundary = null; this._mercBbox = null; // if (this.options.trackAttribution) { // this._attributionRemoved = true; // this.getAttribution = null; // } L.setOptions(this, options); }, /*onAdd: function(map) { this._crs = this.options.crs || map.options.crs; L.TileLayer.prototype.onAdd.call(this, map); },*/ getTileUrl: function(coords) { var tileSize = this.options.tileSize; var nwPoint = coords.multiplyBy(tileSize); nwPoint.x += 1; nwPoint.y -= 1; var sePoint = nwPoint.add(new L.Point(tileSize, tileSize)); var zoom = this._tileZoom; var nw = this._crs.project(this._map.unproject(nwPoint, zoom)); var se = this._crs.project(this._map.unproject(sePoint, zoom)); var tilewidth = se.x - nw.x; var ident = this.matrixIds[zoom].identifier; var tilematrix = ident; //this.wmtsParams.tilematrixSet + ":" + ident //bug在加载天地图时会出错,在加载geoserver发布的wmts时又有用 var X0 = this.matrixIds[zoom].topLeftCorner.lng; var Y0 = this.matrixIds[zoom].topLeftCorner.lat; var tilecol = Math.floor((nw.x - X0) / tilewidth); var tilerow = -Math.floor((nw.y - Y0) / tilewidth); var url = L.Util.template(this._url, { s: this._getSubdomain(coords) }); return url + L.Util.getParamString(this.wmtsParams, url) + "&tilematrix=" + tilematrix + "&tilerow=" + tilerow + "&tilecol=" + tilecol; }, setParams: function(params, noRedraw) { L.extend(this.wmtsParams, params); if (!noRedraw) { this.redraw(); } return this; }, getDefaultMatrix: function() { var matrixIds3857 = new Array(22); for (var i = 0; i < 22; i++) { matrixIds3857[i] = { identifier: "" + i, topLeftCorner: new L.LatLng(90, -180) }; } return matrixIds3857; }, //编辑 createTile: function(coords, done){ var tile = document.createElement('canvas'), url = this.getTileUrl(coords); tile.width = tile.height = this.options.tileSize; this._drawTileInternal(tile, coords, url, L.bind(done, null, null, tile)); return tile; } }); L.tileLayer.wmts = function(url, options) { return new L.TileLayer.WMTS(url, options); };
- 自定义wms以及wmts类调用
//wmts图层 var wmtsLayer = new L.TileLayer.WMTS('http://t{s}.tianditu.gov.cn/img_c/wmts?tk=7786923a385369346d56b966bb6ad62f', { tileSize: 256, layer: "img", tilematrixSet: "c", format: "tile", matrixIds: matrixIds, subdomains: ["0", "1", "2", "3", "4", "5", "6", "7"], boundary: geojson, }); map.addLayer(wmtsLayer); //wms图层 var wmsLayer = L.tileLayer.gishomewms("http://localhost:8080/geoserver/ZKYGIS/wms?", { layers: "ZKYGIS:district_code", //需要加载的图层 format: "image/png", //返回的数据格式 transparent: true, maxZoom: 21, boundary: geojson }); map.addLayer(wmsLayer);
完整demo源码见小专栏文章尾部:小专栏
文章尾部提供源代码下载,对本专栏感兴趣的话,可以关注一波