Files
kxfx/public/map/mars3d/plugins/heatmap/mars3d-heatmap-src.js
2025-09-10 00:13:57 +08:00

648 lines
22 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/**
* 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} isFormattrue时返回格式化对象isFormatfalse时返回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 });
}));