UNPKG

echarts-extension-leaflet

Version:
795 lines (670 loc) 27.3 kB
(function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, require('echarts/lib/echarts'), require('leaflet/src/Leaflet')) : typeof define === 'function' && define.amd ? define(['exports', 'echarts/lib/echarts', 'leaflet/src/Leaflet'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global.leaflet = {}, global.echarts, global.L)); })(this, (function (exports, echarts, L) { 'use strict'; // 这段代码来自于 https://github.com/Leaflet/Leaflet.TileLayer.NoGap/blob/master/L.TileLayer.NoGap.js // 如果没有这段,则显示的地图的瓦片之间有空白线 // @class TileLayer L.TileLayer.mergeOptions({ // @option keepBuffer // The amount of tiles outside the visible map area to be kept in the stitched // `TileLayer`. // @option dumpToCanvas: Boolean = true // Whether to dump loaded tiles to a `<canvas>` to prevent some rendering // artifacts. (Disabled by default in IE) dumpToCanvas: L.Browser.canvas && !L.Browser.ie }); L.TileLayer.include({ _onUpdateLevel: function _onUpdateLevel(z, zoom) { if (this.options.dumpToCanvas) { this._levels[z].canvas.style.zIndex = this.options.maxZoom - Math.abs(zoom - z); } }, _onRemoveLevel: function _onRemoveLevel(z) { if (this.options.dumpToCanvas) { L.DomUtil.remove(this._levels[z].canvas); } }, _onCreateLevel: function _onCreateLevel(level) { if (this.options.dumpToCanvas) { level.canvas = L.DomUtil.create("canvas", "leaflet-tile-container leaflet-zoom-animated", this._container); level.ctx = level.canvas.getContext("2d"); this._resetCanvasSize(level); } }, _removeTile: function _removeTile(key) { if (this.options.dumpToCanvas) { var tile = this._tiles[key]; var level = this._levels[tile.coords.z]; var tileSize = this.getTileSize(); if (level) { // Where in the canvas should this tile go? var offset = L.point(tile.coords.x, tile.coords.y).subtract(level.canvasRange.min).scaleBy(this.getTileSize()); level.ctx.clearRect(offset.x, offset.y, tileSize.x, tileSize.y); } } L.GridLayer.prototype._removeTile.call(this, key); }, _resetCanvasSize: function _resetCanvasSize(level) { var buff = this.options.keepBuffer, pixelBounds = this._getTiledPixelBounds(this._map.getCenter()), tileRange = this._pxBoundsToTileRange(pixelBounds), tileSize = this.getTileSize(); tileRange.min = tileRange.min.subtract([buff, buff]); // This adds the no-prune buffer tileRange.max = tileRange.max.add([buff + 1, buff + 1]); var pixelRange = L.bounds(tileRange.min.scaleBy(tileSize), tileRange.max.add([1, 1]).scaleBy(tileSize) // This prevents an off-by-one when checking if tiles are inside ), mustRepositionCanvas = false, neededSize = pixelRange.max.subtract(pixelRange.min); // Resize the canvas, if needed, and only to make it bigger. if (neededSize.x > level.canvas.width || neededSize.y > level.canvas.height) { // Resizing canvases erases the currently drawn content, I'm afraid. // To keep it, dump the pixels to another canvas, then display it on // top. This could be done with getImageData/putImageData, but that // would break for tainted canvases (in non-CORS tilesets) var oldSize = { x: level.canvas.width, y: level.canvas.height }; // console.info('Resizing canvas from ', oldSize, 'to ', neededSize); var tmpCanvas = L.DomUtil.create("canvas"); tmpCanvas.style.width = (tmpCanvas.width = oldSize.x) + "px"; tmpCanvas.style.height = (tmpCanvas.height = oldSize.y) + "px"; tmpCanvas.getContext("2d").drawImage(level.canvas, 0, 0); // var data = level.ctx.getImageData(0, 0, oldSize.x, oldSize.y); level.canvas.style.width = (level.canvas.width = neededSize.x) + "px"; level.canvas.style.height = (level.canvas.height = neededSize.y) + "px"; level.ctx.drawImage(tmpCanvas, 0, 0); // level.ctx.putImageData(data, 0, 0, 0, 0, oldSize.x, oldSize.y); } // Translate the canvas contents if it's moved around if (level.canvasRange) { var offset = level.canvasRange.min.subtract(tileRange.min).scaleBy(this.getTileSize()); // console.info('Offsetting by ', offset); if (!L.Browser.safari) { // By default, canvases copy things "on top of" existing pixels, but we want // this to *replace* the existing pixels when doing a drawImage() call. // This will also clear the sides, so no clearRect() calls are needed to make room // for the new tiles. level.ctx.globalCompositeOperation = "copy"; level.ctx.drawImage(level.canvas, offset.x, offset.y); level.ctx.globalCompositeOperation = "source-over"; } else { // Safari clears the canvas when copying from itself :-( if (!this._tmpCanvas) { var t = this._tmpCanvas = L.DomUtil.create("canvas"); t.width = level.canvas.width; t.height = level.canvas.height; this._tmpContext = t.getContext("2d"); } this._tmpContext.clearRect(0, 0, level.canvas.width, level.canvas.height); this._tmpContext.drawImage(level.canvas, 0, 0); level.ctx.clearRect(0, 0, level.canvas.width, level.canvas.height); level.ctx.drawImage(this._tmpCanvas, offset.x, offset.y); } mustRepositionCanvas = true; // Wait until new props are set } level.canvasRange = tileRange; level.canvasPxRange = pixelRange; level.canvasOrigin = pixelRange.min; // console.log('Canvas tile range: ', level, tileRange.min, tileRange.max ); // console.log('Canvas pixel range: ', pixelRange.min, pixelRange.max ); // console.log('Level origin: ', level.origin ); if (mustRepositionCanvas) { this._setCanvasZoomTransform(level, this._map.getCenter(), this._map.getZoom()); } }, /// set transform/position of canvas, in addition to the transform/position of the individual tile container _setZoomTransform: function _setZoomTransform(level, center, zoom) { L.GridLayer.prototype._setZoomTransform.call(this, level, center, zoom); if (this.options.dumpToCanvas) { this._setCanvasZoomTransform(level, center, zoom); } }, // This will get called twice: // * From _setZoomTransform // * When the canvas has shifted due to a new tile being loaded _setCanvasZoomTransform: function _setCanvasZoomTransform(level, center, zoom) { // console.log('_setCanvasZoomTransform', level, center, zoom); if (!level.canvasOrigin) { return; } var scale = this._map.getZoomScale(zoom, level.zoom), translate = level.canvasOrigin.multiplyBy(scale).subtract(this._map._getNewPixelOrigin(center, zoom)).round(); if (L.Browser.any3d) { L.DomUtil.setTransform(level.canvas, translate, scale); } else { L.DomUtil.setPosition(level.canvas, translate); } }, _onOpaqueTile: function _onOpaqueTile(tile) { if (!this.options.dumpToCanvas) { return; } // Guard against an NS_ERROR_NOT_AVAILABLE (or similar) exception // when a non-image-tile has been loaded (e.g. a WMS error). // Checking for tile.el.complete is not enough, as it has been // already marked as loaded and ready somehow. try { this.dumpPixels(tile.coords, tile.el); } catch (ex) { return this.fire("tileerror", { error: "Could not copy tile pixels: " + ex, tile: tile, coods: tile.coords }); } // If dumping the pixels was successful, then hide the tile. // Do not remove the tile itself, as it is needed to check if the whole // level (and its canvas) should be removed (via level.el.children.length) tile.el.style.display = "none"; }, // @section Extension methods // @uninheritable // @method dumpPixels(coords: Object, imageSource: CanvasImageSource): this // Dumps pixels from the given `CanvasImageSource` into the layer, into // the space for the tile represented by the `coords` tile coordinates (an object // like `{x: Number, y: Number, z: Number}`; the image source must have the // same size as the `tileSize` option for the layer. Has no effect if `dumpToCanvas` // is `false`. dumpPixels: function dumpPixels(coords, imageSource) { var level = this._levels[coords.z], tileSize = this.getTileSize(); if (!level.canvasRange || !this.options.dumpToCanvas) { return; } // Check if the tile is inside the currently visible map bounds // There is a possible race condition when tiles are loaded after they // have been panned outside of the map. if (!level.canvasRange.contains(coords)) { this._resetCanvasSize(level); } // Where in the canvas should this tile go? var offset = L.point(coords.x, coords.y).subtract(level.canvasRange.min).scaleBy(this.getTileSize()); level.ctx.drawImage(imageSource, offset.x, offset.y, tileSize.x, tileSize.y); // TODO: Clear the pixels of other levels' canvases where they overlap // this newly dumped tile. return this; } }); var classCallCheck = function (instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }; var createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); var get = function get(object, property, receiver) { if (object === null) object = Function.prototype; var desc = Object.getOwnPropertyDescriptor(object, property); if (desc === undefined) { var parent = Object.getPrototypeOf(object); if (parent === null) { return undefined; } else { return get(parent, property, receiver); } } else if ("value" in desc) { return desc.value; } else { var getter = desc.get; if (getter === undefined) { return undefined; } return getter.call(receiver); } }; var inherits = function (subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; }; var possibleConstructorReturn = function (self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; }; // 来源这里: https://gitee.com/qwswqwe/leaflet-user-define-theme // 对应的文章在这里:https://blog.csdn.net/qq_58771242/article/details/132981229 // 用于修改图层的颜色,如果在options中有color选项,则会使用该颜色来对图片的颜色做反转 // 有了这个思路之后,如果以后需要对瓦片做什么特殊处理,比如叠加显示一个特定的文本,也是可以的 var DesignTileLayer = function (_L$TileLayer) { inherits(DesignTileLayer, _L$TileLayer); function DesignTileLayer() { classCallCheck(this, DesignTileLayer); return possibleConstructorReturn(this, (DesignTileLayer.__proto__ || Object.getPrototypeOf(DesignTileLayer)).apply(this, arguments)); } createClass(DesignTileLayer, [{ key: "createTile", value: function createTile(coords, done) { var _this2 = this; var color = this.options.color; if (!color) { return get(DesignTileLayer.prototype.__proto__ || Object.getPrototypeOf(DesignTileLayer.prototype), "createTile", this).call(this, coords, done); } /**获取外部接受的颜色*/ console.warn("createTile", this.options); console.warn("createTile color", color); // 创建一个用于绘图的 <canvas> 元素 var tile = L.DomUtil.create("canvas", "leaflet-tile"); // 根据选项设置瓦片的宽度和高度 var size = this.getTileSize(); tile.width = size.x; tile.height = size.y; // 获得一个 canvas 上下文,并使用 coords.x、coords.y 和 coords.z 在上面画东西 var ctx = tile.getContext("2d"); // 使用传入的 URL 模板替换变量 var url = this.getTileUrl(coords); // 创建一个图像对象来加载瓦片 var img = new Image(); // 解决跨域的问题,https://blog.csdn.net/leftfist/article/details/106947101 img.setAttribute("crossOrigin", ""); // 以下是TileLayer的createTile方法内对于跨域的处理,可以考虑参考下 // https://github.com/Leaflet/Leaflet/blob/21d27856f2e314d267ea6120363eb60660bae123/src/layer/tile/TileLayer.js#L145 // if (this.options.crossOrigin || this.options.crossOrigin === "") { // tile.crossOrigin = // this.options.crossOrigin === true ? "" : this.options.crossOrigin; // } // // for this new option we follow the documented behavior // // more closely by only setting the property when string // if (typeof this.options.referrerPolicy === "string") { // tile.referrerPolicy = this.options.referrerPolicy; // } img.src = url; // 替换为你的图片路径 // 当图片加载完成后,绘制到 Canvas 上 img.onload = function () { // 绘制图片到 Canvas 上 console.warn("img onLoad", tile.width, tile.height); ctx.drawImage(img, 0, 0, tile.width, tile.height); // 获取图像的像素数据 var imageData = ctx.getImageData(0, 0, tile.width, tile.height); if (color) { // 获取原来的图片的像素颜色 var pixels = imageData.data; for (var i = 0; i < pixels.length; i += 4) { var r = pixels[i], g = pixels[i + 1], b = pixels[i + 2], a = pixels[i + 3]; //计算灰度 var grayVal = (r + g + b) / 3; //灰度反转--会使图片整体变成灰色--方便上色 grayVal = 255 - grayVal; //将灰度替换掉原始的颜色 pixels[i] = grayVal + color.r; pixels[i + 1] = grayVal + color.g; pixels[i + 2] = grayVal + color.b; //设置一个前景透明度,以便和背景混合 if (color.a) { pixels[i + 3] = a * color.a; } } } // 将修改后的像素数据放回 Canvas ctx.putImageData(imageData, 0, 0); _this2._tileOnLoad(done, img); }; // 返回瓦片,以便在屏幕上呈现 return tile; } }]); return DesignTileLayer; }(L.TileLayer); /** * constructor for Leaflet CoordSys * @param {L.map} map * @param {Object} api */ function LeafletCoordSys(map, api) { this._map = map; this.dimensions = ['lng', 'lat']; this._mapOffset = [0, 0]; this._api = api; this._projection = L.Projection.Mercator; } LeafletCoordSys.prototype.dimensions = ['lng', 'lat']; LeafletCoordSys.prototype.setZoom = function (zoom) { this._zoom = zoom; }; LeafletCoordSys.prototype.setCenter = function (center) { this._center = this._projection.project(new L.LatLng(center[1], center[0])); }; LeafletCoordSys.prototype.setMapOffset = function (mapOffset) { this._mapOffset = mapOffset; }; LeafletCoordSys.prototype.getLeaflet = function () { return this._map; }; LeafletCoordSys.prototype.dataToPoint = function (data) { var point = new L.LatLng(data[1], data[0]); var px = this._map.latLngToLayerPoint(point); var mapOffset = this._mapOffset; return [px.x - mapOffset[0], px.y - mapOffset[1]]; }; LeafletCoordSys.prototype.pointToData = function (pt) { var mapOffset = this._mapOffset; var coord = this._map.layerPointToLatLng({ x: pt[0] + mapOffset[0], y: pt[1] + mapOffset[1] }); return [coord.lng, coord.lat]; }; LeafletCoordSys.prototype.convertToPixel = echarts.util.curry(doConvert, 'dataToPoint'); LeafletCoordSys.prototype.convertFromPixel = echarts.util.curry(doConvert, 'pointToData'); /** * find appropriate coordinate system to convert * @param {*} methodName * @param {*} ecModel * @param {*} finder * @param {*} value * @return {*} converted value */ function doConvert(methodName, ecModel, finder, value) { var leafletModel = finder.leafletModel; var seriesModel = finder.seriesModel; var coordSys = leafletModel ? leafletModel.coordinateSystem : seriesModel ? seriesModel.coordinateSystem || // For map. (seriesModel.getReferringComponents('leaflet')[0] || {}).coordinateSystem : null; /* eslint-disable no-invalid-this */ return coordSys === this ? coordSys[methodName](value) : null; } LeafletCoordSys.prototype.getViewRect = function () { var api = this._api; return new echarts.graphic.BoundingRect(0, 0, api.getWidth(), api.getHeight()); }; LeafletCoordSys.prototype.getRoamTransform = function () { return echarts.matrix.create(); }; LeafletCoordSys.dimensions = LeafletCoordSys.prototype.dimensions; var CustomOverlay = L.Layer.extend({ initialize: function initialize(container) { this._container = container; }, onAdd: function onAdd(map) { var pane = map.getPane(this.options.pane); pane.appendChild(this._container); // Calculate initial position of container with // `L.Map.latLngToLayerPoint()`, `getPixelOrigin() // and/or `getPixelBounds()` // L.DomUtil.setPosition(this._container, point); // Add and position children elements if needed // map.on('zoomend viewreset', this._update, this); }, onRemove: function onRemove(map) { L.DomUtil.remove(this._container); // map.off('zoomend viewreset', this._update, this); }, _update: function _update() { // Recalculate position of container // L.DomUtil.setPosition(this._container, point); // Add/remove/reposition children elements if needed } }); LeafletCoordSys.create = function (ecModel, api) { var leafletCoordSys = void 0; var leafletList = []; var root = api.getDom(); // TODO Dispose ecModel.eachComponent('leaflet', function (leafletModel) { var viewportRoot = api.getZr().painter.getViewportRoot(); if (typeof L === 'undefined') { throw new Error('Leaflet api is not loaded'); } if (leafletCoordSys) { throw new Error('Only one leaflet component can exist'); } if (!leafletModel.__map) { // Not support IE8 var mapRoot = root.querySelector('.ec-extension-leaflet'); if (mapRoot) { // Reset viewport left and top, which will be changed // in moving handler in LeafletView viewportRoot.style.left = '0px'; viewportRoot.style.top = '0px'; root.removeChild(mapRoot); } mapRoot = document.createElement('div'); mapRoot.style.cssText = 'width:100%;height:100%'; // Not support IE8 mapRoot.classList.add('ec-extension-leaflet'); root.appendChild(mapRoot); var _map = leafletModel.__map = L.map(mapRoot, leafletModel.get('leafletOption')); var tiles = leafletModel.get('tiles'); var baseLayers = {}; var baseLayerAdded = false; var _iteratorNormalCompletion = true; var _didIteratorError = false; var _iteratorError = undefined; try { for (var _iterator = tiles[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) { var tile = _step.value; // let tileLayer = L.tileLayer(tile.urlTemplate, tile.options); var tileLayer = new DesignTileLayer(tile.urlTemplate, tile.options); if (tile.label) { // only add one baseLayer if (!baseLayerAdded) { tileLayer.addTo(_map); baseLayerAdded = true; } baseLayers[tile.label] = tileLayer; } else { // add all tiles without labels into the map tileLayer.addTo(_map); } } // add layer control when there are more than two layers } catch (err) { _didIteratorError = true; _iteratorError = err; } finally { try { if (!_iteratorNormalCompletion && _iterator.return) { _iterator.return(); } } finally { if (_didIteratorError) { throw _iteratorError; } } } if (tiles.length > 1) { var layerControlOpts = leafletModel.get('layerControl'); L.control.layers(baseLayers, {}, layerControlOpts).addTo(_map); } /* Encapsulate viewportRoot element into the parent element responsible for moving, avoiding direct manipulation of viewportRoot elements affecting related attributes such as offset. */ var moveContainer = document.createElement('div'); moveContainer.style = 'position: relative;'; moveContainer.appendChild(viewportRoot); new CustomOverlay(moveContainer).addTo(_map); } var map = leafletModel.__map; // Set leaflet options // centerAndZoom before layout and render var center = leafletModel.get('center'); var zoom = leafletModel.get('zoom'); if (center && zoom) { map.setView([center[1], center[0]], zoom); } leafletCoordSys = new LeafletCoordSys(map, api); leafletList.push(leafletCoordSys); leafletCoordSys.setMapOffset(leafletModel.__mapOffset || [0, 0]); leafletCoordSys.setZoom(zoom); leafletCoordSys.setCenter(center); leafletModel.coordinateSystem = leafletCoordSys; }); ecModel.eachSeries(function (seriesModel) { if (seriesModel.get('coordinateSystem') === 'leaflet') { seriesModel.coordinateSystem = leafletCoordSys; } }); return leafletList; }; /** * compare if two arrays of length 2 are equal * @param {Array} a array of length 2 * @param {Array} b array of length 2 * @return {Boolean} */ function v2Equal(a, b) { return a && b && a[0] === b[0] && a[1] === b[1]; } echarts.extendComponentModel({ type: 'leaflet', getLeaflet: function getLeaflet() { // __map is injected when creating LeafletCoordSys return this.__map; }, setCenterAndZoom: function setCenterAndZoom(center, zoom) { this.option.center = center; this.option.zoom = zoom; }, centerOrZoomChanged: function centerOrZoomChanged(center, zoom) { var option = this.option; return !(v2Equal(center, option.center) && zoom === option.zoom); }, defaultOption: { center: [104.114129, 37.550339], zoom: 2, // 除了center和zoom以外的其他leaflet地图选项,都放在这里 leafletOption: { zoomControl: false }, roam: false, layerControl: {}, tiles: [{ urlTemplate: 'http://{s}.tile.osm.org/{z}/{x}/{y}.png', options: { attribution: '&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors' } }] } }); echarts.extendComponentView({ type: 'leaflet', render: function render(leafletModel, ecModel, api) { var rendering = true; var leaflet = leafletModel.getLeaflet(); var moveContainer = api.getZr().painter.getViewportRoot().parentNode; var coordSys = leafletModel.coordinateSystem; var moveHandler = function moveHandler(type, target) { if (rendering) { return; } var offsetEl = leaflet._mapPane; // calculate new mapOffset var transformStyle = offsetEl.style.transform; var dx = 0; var dy = 0; if (transformStyle) { transformStyle = transformStyle.replace('translate3d(', ''); var parts = transformStyle.split(','); dx = -parseInt(parts[0], 10); dy = -parseInt(parts[1], 10); } else { // browsers that don't support transform: matrix dx = -parseInt(offsetEl.style.left, 10); dy = -parseInt(offsetEl.style.top, 10); } var mapOffset = [dx, dy]; moveContainer.style.left = mapOffset[0] + 'px'; moveContainer.style.top = mapOffset[1] + 'px'; coordSys.setMapOffset(mapOffset); leafletModel.__mapOffset = mapOffset; api.dispatchAction({ type: 'leafletRoam' }); }; /** * handler for map zoomEnd event */ function zoomEndHandler() { if (rendering) return; api.dispatchAction({ type: 'leafletRoam' }); } /** * handler for map zoom event */ function zoomHandler() { moveHandler(); } if (this._oldMoveHandler) { leaflet.off('move', this._oldMoveHandler); } if (this._oldZoomHandler) { leaflet.off('zoom', this._oldZoomHandler); } if (this._oldZoomEndHandler) { leaflet.off('zoomend', this._oldZoomEndHandler); } leaflet.on('move', moveHandler); leaflet.on('zoom', zoomHandler); leaflet.on('zoomend', zoomEndHandler); this._oldMoveHandler = moveHandler; this._oldZoomHandler = zoomHandler; this._oldZoomEndHandler = zoomEndHandler; var roam = leafletModel.get('roam'); // can move if (roam && roam !== 'scale') { leaflet.dragging.enable(); } else { leaflet.dragging.disable(); } // can zoom (may need to be more fine-grained) if (roam && roam !== 'move') { leaflet.scrollWheelZoom.enable(); leaflet.doubleClickZoom.enable(); leaflet.touchZoom.enable(); } else { leaflet.scrollWheelZoom.disable(); leaflet.doubleClickZoom.disable(); leaflet.touchZoom.disable(); } rendering = false; } }); /** * Leftlet component extension */ echarts.registerCoordinateSystem('leaflet', LeafletCoordSys); echarts.registerAction({ type: 'leafletRoam', event: 'leafletRoam', update: 'updateLayout' }, function (payload, ecModel) { ecModel.eachComponent('leaflet', function (leafletModel) { var leaflet = leafletModel.getLeaflet(); var center = leaflet.getCenter(); leafletModel.setCenterAndZoom([center.lng, center.lat], leaflet.getZoom()); }); }); var version = '1.0.0'; exports.version = version; }));