/** * Mars3D平台插件,结合heatmap可视化功能插件 mars3d-heatmap * * 版本信息:v3.4.7 * 编译日期:2022-09-15 16:25:35 * 版权所有:Copyright by 火星科技 http://mars3d.cn * 使用单位:安徽XX有限公司 ,2021-08-18 */ (function (global, factory) { typeof exports === 'object' && typeof module !== 'undefined' ? factory(exports, (window.mars3d || require('mars3d')), (window.h337 || require('@mars3d/heatmap.js'))) : typeof define === 'function' && define.amd ? define(['exports', 'mars3d', '@mars3d/heatmap.js'], factory) : (global = typeof globalThis !== 'undefined' ? globalThis : global || self, factory(global["mars3d-heatmap"] = {}, global.mars3d, global.h337)); })(this, (function (exports, mars3d, h337) { 'use strict'; function _interopNamespace(e) { if (e && e.__esModule) return e; var n = Object.create(null); if (e) { Object.keys(e).forEach(function (k) { if (k !== 'default') { var d = Object.getOwnPropertyDescriptor(e, k); Object.defineProperty(n, k, d.get ? d : { enumerable: true, get: function () { return e[k]; } }); } }); } n["default"] = e; return n; } var mars3d__namespace = /*#__PURE__*/_interopNamespace(mars3d); var h337__namespace = /*#__PURE__*/_interopNamespace(h337); var HeatMaterial = "uniform sampler2D image;\n\nczm_material czm_getMaterial(czm_materialInput materialInput) {\n czm_material material = czm_getDefaultMaterial(materialInput);\n vec2 st = materialInput.st;\n vec4 colorImage = texture2D(image, st);\n if(colorImage.rgb == vec3(1.0) || colorImage.rgb == vec3(0.0)) {\n discard;\n }\n material.diffuse = colorImage.rgb;\n material.alpha = colorImage.a;\n return material;\n}\n"; // eslint-disable-line if (!h337__namespace.create) { throw new Error("请引入 heatmap.js 库 ") } const Cesium = mars3d__namespace.Cesium; const BaseLayer = mars3d__namespace.layer.BaseLayer; // 热力图默认参数 const DEF_HEATSTYLE = { maxOpacity: 0.8, minOpacity: 0.1, blur: 0.85, radius: 25, gradient: { 0.4: "blue", 0.6: "green", 0.8: "yellow", 0.9: "red" } }; const DEF_STYLE = { arcRadiusScale: 1.5, arcBlurScale: 1.5, vertexFormat: Cesium.EllipsoidSurfaceAppearance.VERTEX_FORMAT }; /** * 热力图图层,基于heatmap.js库渲染。 * 【需要引入 heatmap.js 库 和 mars3d-heatmap 插件库】 * * @param {Object} options 参数对象,包括以下: * @param {LngLatPoint[]|Cesium.Cartesian3[]|Object[]} [options.positions] 坐标数据集合(含value热力值),有热力值时,传入LatLngPoint数组,热力值为value字段。示例:[{lat:31.123,lng:103.568,value:1.2},{lat:31.233,lng:103.938,value:2.3}] * * @param {Object} [options.rectangle] 坐标的矩形区域范围,默认内部自动计算 * @param {Number} options.rectangle.xmin 最小经度值 * @param {Number} options.rectangle.xmax 最大经度值 * @param {Number} options.rectangle.ymin 最小纬度值 * @param {Number} options.rectangle.ymax 最大纬度值 * * @param {Number} [options.max] 数据集的value值上限,默认内部计算 * @param {Number} [options.min] 数据集的value值下限,默认内部计算 * * @param {Object} [options.heatStyle] heatmap热力图本身configObject参数,详情也可查阅 [heatmap文档]{@link https://www.patrick-wied.at/static/heatmapjs/docs.html} * @param {Number} [options.heatStyle.maxOpacity=0.8] 最大不透明度,取值范围0.0-1.0。 * @param {Number} [options.heatStyle.minOpacity=0.1] 最小不透明度,取值范围0.0-1.0。 * @param {Number} [options.heatStyle.blur=0.85] 将应用于所有数据点的模糊因子。模糊因子越高,渐变将越平滑 * @param {Number} [options.heatStyle.radius=25] 每个数据点将具有的半径(如果未在数据点本身上指定) * @param {Object} [options.heatStyle.gradient] 色带,表示渐变的对象,示例:{ 0.4: 'blue', 0.6: 'green',0.8: 'yellow',0.9: 'red' } * * @param {RectanglePrimitive.StyleOptions|Object} [options.style] 矢量对象样式参数,还包括: * @param {Boolean} [options.style.opacity=1] 透明度 * @param {Boolean} [options.style.arc=false] 是否显示曲面热力图 * @param {Boolean} [options.style.arcRadiusScale=1.5] 曲面热力图时,radius扩大比例 * @param {Boolean} [options.style.arcBlurScale=1.5] 曲面热力图时,blur扩大比例 * @param {Number} [options.style.height = 0] 高度,相对于椭球面的高度。 * @param {Number} [options.style.diffHeight ] 曲面的起伏差值高,默认根据数据范围的比例自动计算。 * @param {RectanglePrimitive.StyleOptions} [options.style.多个参数] rectangle矩形支持的样式 * * @param {Number} [options.maxCanvasSize=5000] Canvas最大尺寸(单位:像素),调大精度更高,但过大容易内存溢出 * @param {Number} [options.minCanvasSize=700] Canvas最小尺寸(单位:像素) * @param {Number} [options.delayTime=2] 显示数据时的过渡动画时长(单位:秒) * * @param {String|Number} [options.id = createGuid()] 图层id标识 * @param {String|Number} [options.pid = -1] 图层父级的id,一般图层管理中使用 * @param {String} [options.name = ''] 图层名称 * @param {Boolean} [options.show = true] 图层是否显示 * @param {BaseClass|Boolean} [options.eventParent] 指定的事件冒泡对象,默认为map对象,false时不冒泡 * @param {Object} [options.center] 图层自定义定位视角 {@link Map#setCameraView} * @param {Number} options.center.lng 经度值, 180 - 180 * @param {Number} options.center.lat 纬度值, -90 - 90 * @param {Number} [options.center.alt] 高度值 * @param {Number} [options.center.heading] 方向角度值,绕垂直于地心的轴旋转角度, 0至360 * @param {Number} [options.center.pitch] 俯仰角度值,绕纬度线旋转角度, -90至90 * @param {Number} [options.center.roll] 翻滚角度值,绕经度线旋转角度, -90至90 * @param {Boolean} [options.flyTo] 加载完成数据后是否自动飞行定位到数据所在的区域。 * @export * @class HeatLayer * @extends {BaseLayer} */ class HeatLayer extends BaseLayer { constructor(options = {}) { super(options); this.options.maxCanvasSize = this.options.maxCanvasSize ?? document.body.clientWidth; this.options.maxCanvasSize = Math.min(this.options.maxCanvasSize, 5000); this.options.minCanvasSize = this.options.minCanvasSize ?? document.body.clientHeight; this.options.minCanvasSize = Math.max(this.options.minCanvasSize, 700); this.options.heatStyle = { ...DEF_HEATSTYLE, ...(this.options.heatStyle || {}) }; this.options.style = { ...DEF_STYLE, ...(this.options.style || {}) }; } /** * 矢量数据图层 * @type {GraphicLayer} * @readonly */ get layer() { return this._layer } /** * heatmap热力图本身configObject参数,详情也可查阅 [heatmap文档]{@link https://www.patrick-wied.at/static/heatmapjs/docs.html} * @type {Object} */ get heatStyle() { return this.options.heatStyle } set heatStyle(value) { this.options.heatStyle = mars3d__namespace.Util.merge(this.options.heatStyle, value); if (this._heat) { this._heat.configure(this.options.heatStyle); this._updatePositionsHook(true); } } /** * 矩形的样式参数 * @type {RectanglePrimitive.StyleOptions} */ get style() { return this.options.style } set style(value) { this.options.style = mars3d__namespace.Util.merge(this.options.style, value); } /** * 坐标数据集合(含value热力值),示例:[{lat:31.123,lng:103.568,value:1.2},{lat:31.233,lng:103.938,value:2.3}] 。 * 平滑更新建议使用setPositions方法 * @type {Cesium.Cartesian3[]|LngLatPoint[]} */ get positions() { return this._positions } set positions(value) { this.setPositions(value); } /** * 位置坐标(数组对象),示例 [ [123.123456,32.654321,198.7], [111.123456,22.654321,50.7] ] * @type {Array[]} * @readonly */ get coordinates() { const coords = []; this.points.forEach((item) => { coords.push(item.toArray()); }); return coords } /** * 坐标数据对应的矩形边界 * @type {Cesium.Rectangle} * @readonly */ get rectangle() { return this._rectangle } _setOptionsHook(options, newOptions) { if (options.positions) { this.positions = options.positions; } } /** * 对象添加到地图前创建一些对象的钩子方法, * 只会调用一次 * @return {void} 无 * @private */ _mountedHook() { this._layer = new mars3d__namespace.layer.GraphicLayer({ private: true }); } /** * 对象添加到地图上的创建钩子方法, * 每次add时都会调用 * @return {void} 无 * @private */ _addedHook() { this._map.addLayer(this._layer); if (this.options.positions) { this.positions = this.options.positions; } if (this.options.flyTo) { this.flyToByAnimationEnd(); } } /** * 对象从地图上移除的创建钩子方法, * 每次remove时都会调用 * @return {void} 无 * @private */ _removedHook() { if (this.heatStyle.container) { mars3d__namespace.DomUtil.remove(this.heatStyle.container); delete this.heatStyle.container; } this.clear(); this._map.removeLayer(this._layer); } /** * 添加新的坐标点(含热力值) * @param {Cesium.Cartesian3 |LngLatPoint} item 坐标点(含热力值),示例: {lat:31.123,lng:103.568,value:1.2} * @param {Boolean} [isGD] 是否固定区域坐标,true时可以平滑更新 * @return {void} 无 */ addPosition(item, isGD) { this._positions = this._positions || []; this._positions.push(item); this._updatePositionsHook(isGD); } /** * 更新所有坐标点(含热力值)数据 * * @param {Cesium.Cartesian3[] |LngLatPoint[]} arr 坐标点(含热力值),示例:[{lat:31.123,lng:103.568,value:1.2},{lat:31.233,lng:103.938,value:2.3}] * @param {Boolean} [isGD] 是否固定区域坐标,true时可以平滑更新 * @return {void} 无 */ setPositions(arr, isGD) { this._positions = arr; this._updatePositionsHook(isGD); } /** * 清除矢量对象 * @return {void} 无 */ clear() { if (this._graphic) { this._layer.removeGraphic(this._graphic, true); delete this._graphic; } } _updatePositionsHook(isGD) { if (!this.show || !this._map || !this.positions || this.positions.length === 0) { return this } const canvas = this._getHeatCanvas(); if (this.style.arc) { if (this._graphic && isGD) { this._graphic.uniforms.image = canvas; this._graphic.uniforms.bumpMap = this._getArcHeatCanvas(); } else { this._createArcGraphic(canvas); } } else { if (this._graphic && isGD) { this._graphic.uniforms.image = canvas; } else { this._createGraphic(canvas); } } return this } // 普通平面热力图 _createGraphic(image) { this.clear(); this._graphic = new mars3d__namespace.graphic.RectanglePrimitive({ ...this.options, rectangle: this._rectangle, appearance: new Cesium.EllipsoidSurfaceAppearance({ material: new Cesium.Material({ fabric: { uniforms: { image: image }, source: HeatMaterial }, translucent: true }), flat: true }) }); this._layer.addGraphic(this._graphic); } // 曲面 热力图 _createArcGraphic(canvas) { this.clear(); const renderstate = Cesium.RenderState.fromCache({ cull: { enabled: true }, depthTest: { enabled: true }, stencilTest: { enabled: true, frontFunction: Cesium.StencilFunction.ALWAYS, frontOperation: { fail: Cesium.StencilOperation.KEEP, zFail: Cesium.StencilOperation.KEEP, zPass: Cesium.StencilOperation.REPLACE }, backFunction: Cesium.StencilFunction.ALWAYS, backOperation: { fail: Cesium.StencilOperation.KEEP, zFail: Cesium.StencilOperation.KEEP, zPass: Cesium.StencilOperation.REPLACE }, reference: 0x2, mask: 0x2 }, blending: Cesium.BlendingState.ALPHA_BLEND }); // 曲面的起伏差值高 const diffHeight = Math.floor(this.style.diffHeight ?? this._mBoundsMax * 0.02) + 0.1; if (this.style.diffHeight) { delete this.style.diffHeight; } // 控制曲面的精度 const splitNum = (this.style.splitNum, 100); let granularity = Math.max(this._rectangle.height, this._rectangle.width); this.style.granularity = granularity /= splitNum; // console.log(this.options.style.granularity) this._graphic = new mars3d__namespace.graphic.RectanglePrimitive({ ...this.options, rectangle: this._rectangle, appearance: new Cesium.EllipsoidSurfaceAppearance({ aboveGround: true, renderState: renderstate, material: new Cesium.Material({ fabric: { uniforms: { image: canvas, repeat: new Cesium.Cartesian2(1.0, 1.0), color: new Cesium.Color(1.0, 1.0, 1.0, 0.01), bumpMap: this._getArcHeatCanvas() }, source: HeatMaterial }, translucent: true }), vertexShaderSource: `attribute vec3 position3DHigh; attribute vec3 position3DLow; attribute vec2 st; attribute float batchId; uniform sampler2D bumpMap_3; varying vec3 v_positionMC; varying vec3 v_positionEC; varying vec2 v_st; void main() { vec4 p = czm_computePosition(); v_positionMC = position3DHigh + position3DLow; v_positionEC = (czm_modelViewRelativeToEye * p).xyz; v_st = st; vec4 color = texture2D(bumpMap_3, v_st); float centerBump = distance(vec3(0.0),color.rgb); vec3 upDir = normalize(v_positionMC.xyz); vec3 disPos = upDir * centerBump * ${diffHeight}; p +=vec4(disPos,0.0); gl_Position = czm_modelViewProjectionRelativeToEye * p; } `, flat: true }) }); this._layer.addGraphic(this._graphic); } /** * 获取图层内所有数据的 矩形边界值 * @param {Boolean} [options] 控制参数 * @param {Boolean} [options.isFormat=false] 是否格式化,格式化时示例: { xmin: 73.16895, xmax: 134.86816, ymin: 12.2023, ymax: 54.11485 } * @return {Cesium.Rectangle|Object} isFormat:true时,返回格式化对象,isFormat:false时返回Cesium.Rectangle对象 */ getRectangle(options) { if (options?.isFormat && this._rectangle) { return mars3d__namespace.PolyUtil.formatRectangle(this._rectangle) } else { return this._rectangle } } // 处理数据并返回生成的图片 _getHeatCanvas() { const positions = this._positions; const _points = []; let xmin, xmax, ymin, ymax; positions.forEach((item) => { const point = mars3d__namespace.LngLatPoint.parse(item); if (!point) { return } point.value = item.value || 1; // 热力值 if (!this.options.rectangle) { if (xmin === undefined) { xmin = point.lng; xmax = point.lng; ymin = point.lat; ymax = point.lat; } else { xmin = Math.min(xmin, point.lng); xmax = Math.max(xmax, point.lng); ymin = Math.min(ymin, point.lat); ymax = Math.max(ymax, point.lat); } } _points.push(point); }); // 经纬度边界 let _bounds = this.options.rectangle || { xmin, xmax, ymin, ymax }; // 墨卡托边界值 const mBounds = getMercatorBounds(_bounds); // 计算范围内的差值和极值 const diffLng = Math.abs(mBounds.xmax - mBounds.xmin); const diffLat = Math.abs(mBounds.ymax - mBounds.ymin); const max = Math.max(diffLng, diffLat); const min = Math.min(diffLng, diffLat); this._mBoundsMax = max; // 计算是否缩放 let scale = 1; if (max > this.options.maxCanvasSize) { scale = max / this.options.maxCanvasSize; if (min / scale < this.options.minCanvasSize) { scale = min / this.options.minCanvasSize; } } else if (min < this.options.minCanvasSize) { scale = min / this.options.minCanvasSize; if (max / scale > this.options.maxCanvasSize) { scale = max / this.options.maxCanvasSize; } } const spacing = this.heatStyle.radius * 1.5; const canvasWidth = diffLng / scale + spacing * 2; const canvasHeight = diffLat / scale + spacing * 2; const offset = spacing * scale; mBounds.xmin -= offset; mBounds.ymin -= offset; mBounds.xmax += offset; mBounds.ymax += offset; this._scale = scale; // 加扩大值后的边界 _bounds = geLatLngBounds(mBounds); this._rectangle = Cesium.Rectangle.fromDegrees(_bounds.xmin, _bounds.ymin, _bounds.xmax, _bounds.ymax); let maxVal = _points[0].value ?? 1; let minVal = _points[0].value ?? 0; const hetdata = []; _points.forEach((point) => { const mkt = mars3d__namespace.PointTrans.lonlat2mercator([point.lng, point.lat]); const value = point.value || 1; const coord_x = Math.round((mkt[0] - mBounds.xmin) / scale); const coord_y = Math.round((mBounds.ymax - mkt[1]) / scale); maxVal = Math.max(maxVal, value); // 求最大值 minVal = Math.min(minVal, value); hetdata.push({ x: coord_x, y: coord_y, value: value }); }); const heatData = { min: this.options.min ?? minVal, max: this.options.max ?? maxVal, data: hetdata }; this._last_heatData = heatData; if ( !this._last_mBounds || mBounds.xmin !== this._last_mBounds.xmin || mBounds.ymin !== this._last_mBounds.ymin || mBounds.xmax !== this._last_mBounds.xmax || mBounds.ymax !== this._last_mBounds.ymax ) { this._last_mBounds = mBounds; if (!this.heatStyle.container) { this.heatStyle.container = mars3d__namespace.DomUtil.create("div", "mars3d-heatmap mars3d-hideDiv", this._map.container); } this.heatStyle.container.style.cssText = `width:${canvasWidth}px;height:${canvasHeight}px;`; if (!this._heat) { this._heat = h337__namespace.create(this.heatStyle); } else { this._heat.configure(this.heatStyle); } } this._heat.setData(heatData); const canvas = mars3d__namespace.DomUtil.copyCanvas(this._heat._renderer.canvas); // 注释不删除,便于动态更新 // if (this.heatStyle.container) { // mars3d.DomUtil.remove(this.heatStyle.container) // delete this.heatStyle.container // } return canvas } _getArcHeatCanvas() { this._heat.configure({ radius: this.heatStyle.radius * this.style.arcRadiusScale, blur: this.heatStyle.blur * this.style.arcBlurScale, gradient: this.heatStyle.gradientArc || { 0.25: "rgb(0,0,0)", 0.55: "rgb(140,140,140)", 0.85: "rgb(216,216,216)", 1.0: "rgb(255,255,255)" } }); const canvasBump = mars3d__namespace.DomUtil.copyCanvas(this._heat._renderer.canvas); this._heat.configure(this.options.heatStyle); // 恢复配置 return canvasBump } /** * 根据坐标点获取其对应的value值和颜色值 * @param {Cesium.Cartesian3 |LngLatPoint} item 坐标点 * @return {Object} 格式为 {"x":2081,"y":767,"value":3,"color":"rgba(209,231,0,195)"} */ getPointData(item) { const point = mars3d__namespace.LngLatPoint.parse(item); if (!point) { return {} } const mkt = mars3d__namespace.PointTrans.lonlat2mercator([point.lng, point.lat]); const mBounds = this._last_mBounds; const coord_x = Math.round((mkt[0] - mBounds.xmin) / this._scale); const coord_y = Math.round((mBounds.ymax - mkt[1]) / this._scale); const value = this._heat.getValueAt({ x: coord_x, y: coord_y }); const rgba = this._heat._renderer.ctx.getImageData(coord_x - 1, coord_y - 1, 1, 1).data; return { x: coord_x, y: coord_y, value: value, color: "rgba(" + rgba[0] + "," + rgba[1] + "," + rgba[2] + "," + rgba[3] + ")" } } } mars3d__namespace.layer.HeatLayer = HeatLayer; mars3d__namespace.LayerUtil.register("heat", HeatLayer); function getMercatorBounds(bounds) { const pt1 = mars3d__namespace.PointTrans.lonlat2mercator([bounds.xmin, bounds.ymin]); const pt2 = mars3d__namespace.PointTrans.lonlat2mercator([bounds.xmax, bounds.ymax]); return { xmin: pt1[0], ymin: pt1[1], xmax: pt2[0], ymax: pt2[1] } } function geLatLngBounds(bounds) { const pt1 = mars3d__namespace.PointTrans.mercator2lonlat([bounds.xmin, bounds.ymin]); const pt2 = mars3d__namespace.PointTrans.mercator2lonlat([bounds.xmax, bounds.ymax]); return { xmin: pt1[0], ymin: pt1[1], xmax: pt2[0], ymax: pt2[1] } } exports.HeatLayer = HeatLayer; Object.defineProperty(exports, '__esModule', { value: true }); }));