import { deepMerge, wgs84ToGcj02Format } from "../util"; export default (hnMap: any) => { const defaultOption = { id: "", position: [], allowDrillPick: true, max: null, min: null, radius: 25, minOpacity: 0.1, maxOpacity: 0.8, opacity: 1, blur: 0.85, gradient: { 0.4: "blue", 0.6: "green", 0.8: "yellow", 0.9: "red" }, scaleByDistance: false, clampToGround: true, data: null, }; class mars3d_class { type: any = "heatMap"; id: any = null; option: any = JSON.parse(JSON.stringify(defaultOption)); config: any = null; layerEntity: any = null; constructor(option: any) { this.id = option.id; deepMerge(this.option, option); this.config = this.formatConfig(this.option); this.layerEntity = new mars3d.layer.HeatLayer( JSON.parse(JSON.stringify(this.config)) ); } formatConfig(option: any) { return { id: option.id, positions: option.position, allowDrillPick: option.allowDrillPick, max: option.max, min: option.min, heatStyle: { radius: option.radius, minOpacity: option.minOpacity, maxOpacity: option.maxOpacity, blur: option.blur, gradient: option.gradient, }, style: { opacity: option.opacity, scaleByDistance: option.scaleByDistance, clampToGround: option.clampToGround, }, redrawZoom: true, flyTo: true, attr: option.data, }; } set(option: any) { deepMerge(this.option, option); this.config = this.formatConfig(this.option); this.layerEntity.setOptions(this.config); } destroy() { this.layerEntity.remove(true); hnMap.map.layerList = hnMap.map.layerList.filter( (v: any) => v.id !== this.id ); } flyTo() { this.layerEntity.flyTo(); } clear() { this.layerEntity.clear(); } setPosition(position: any) { deepMerge(this.option, { position }); this.layerEntity.setPositions(position); } show() { this.layerEntity.show = true; } hide() { this.layerEntity.show = false; } } class gaode_class { id: any = null; option: any = JSON.parse(JSON.stringify(defaultOption)); config: any = null; layerEntity: any = null; constructor(option: any) { this.id = option.id; deepMerge(this.option, option); this.config = this.formatConfig(this.option); this.layerEntity = new AMap.HeatMap(hnMap.map.map, this.config); this.layerEntity.setDataSet({ data: this.config.data, max: this.config.max, min: this.config.min, }); } formatConfig(option: any) { const data = option.position.map((v: any) => { return { lng: v.lng, lat: v.lat, count: v.value }; }); const amapData = wgs84ToGcj02Format(data); return { id: option.id, radius: option.radius, max: option.max, min: option.min, opacity: [option.minOpacity, option.maxOpacity], gradient: option.gradient, data: amapData, extData: { id: option.id, data: option.data, }, }; } set(option: any) { deepMerge(this.option, option); this.config = this.formatConfig(this.option); this.layerEntity.setOptions(this.config); } destroy() { this.layerEntity.setMap(null); hnMap.map.layerList = hnMap.map.layerList.filter( (v: any) => v.id !== this.id ); } flyTo() { let totalLng = 0; let totalLat = 0; this.config.data.map((item: any) => { totalLng += item.lng; totalLat += item.lat; }); const centerLng = totalLng / this.config.data.length; const centerLat = totalLat / this.config.data.length; hnMap.map.map.setCenter([centerLng, centerLat]); } } class siji_class { type: any = "heatMap"; id: any = null; option: any = JSON.parse(JSON.stringify(defaultOption)); config_heatmap: any = null; config_point: any = null; layerEntity: any = null; constructor(option: any) { this.id = option.id; deepMerge(this.option, option); hnMap.map.map.addSource("themeData", { type: "geojson", data: { type: "FeatureCollection", features: this.option.position.map((v: any) => ({ type: "Feature", geometry: { type: "Point", coordinates: [v.lng, v.lat], }, })), }, }); this.config_heatmap = this.formatConfig_heatmap(this.option); this.config_point = this.formatConfig_point(this.option); } formatConfig_heatmap(option: any) { let config: any = {}; config = { id: option.id, type: "heatmap", source: "themeData", maxzoom: 17, paint: { /** * 数据点的影响力,weight=10的点相当于十个weight=1的点 * 下述为插值表达式,输入是点geojson的properties的mag,输出随mag线性增大 */ "heatmap-weight": [ "interpolate", ["linear"], ["get", "value"], 0, 0, 150, 1.5, ], /** * 热力图强度,类似heatmap-weight * 下述为插值表达式,输出随zoom线性变化,zoom为0时值为1,zoom为12时值为3 */ "heatmap-intensity": [ "interpolate", ["linear"], ["zoom"], 0, 1, 17, 1, ], /** * 像素的颜色,必须以heatmap-density(热力图像素的密度)为输入 * 下述为插值表达式,输出随heatmap-density变化而变化 */ "heatmap-color": [ "interpolate", ["linear"], ["heatmap-density"], 0, "rgba(255, 0, 0, 0)", 0.4, option.gradient["0.4"], 0.6, option.gradient["0.6"], 0.8, option.gradient["0.8"], 0.9, option.gradient["0.9"], // 0, // "rgba(255, 0, 0, 0)", // 0.1, // option.gradient["0.4"], // "rgba(0, 30, 255, .6)", // 0.2, // "rgba(7, 208, 255, .6)", // 0.3, // option.gradient["0.6"], //"#2cc946", // 0.4, // "#d5fb0c", // 0.5, // option.gradient["0.8"], //"#e04e4e", // 0.6, // option.gradient["0.9"], //"#f33900", // 0.9, // "rgba(243, 57, 0, .6)", // 1, // "rgba(243, 57, 0, .8)", ], /** * 该值越大,热力图越平滑,信息越不详细。 * 下述为插值表达式,输出随zoom线性变化,zoom为0时值为8,zoom为9时值为20 */ "heatmap-radius": ["interpolate", ["linear"], ["zoom"], 0, 5, 17, 50], /** * 透明度,输出为1则不透明 * 下述为插值表达式,输出随zoom线性变化,zoom为5时值为0.8,zoom为12时值为0.4 */ "heatmap-opacity": [ "interpolate", ["linear"], ["zoom"], 5, option.maxOpacity, //0.8, 17, option.minOpacity, // 0.8 ], }, }; return config; } formatConfig_point(option: any) { let config_point: any = {}; config_point = { id: "earthquakes-point", type: "circle", source: "themeData", minzoom: 17, paint: { "circle-radius": ["interpolate", ["linear"], ["zoom"], 5, 1, 20, 12], "circle-color": "rgb(255, 148, 0)", "circle-stroke-color": "white", "circle-stroke-width": 1, "circle-opacity": ["interpolate", ["linear"], ["zoom"], 9, 0, 20, 1], }, }; return config_point; } } // Cesium热力图实现类 class cesium_class { type: any = "heatMap"; id: any = null; option: any = JSON.parse(JSON.stringify(defaultOption)); config: any = null; layerEntity: any = null; // Cesium热力图相关属性 entities: any[] = []; dataSource: any = null; // 几何实例(高性能方案) geometryInstances: any[] = []; primitive: any = null; // 屏幕空间事件处理器 screenSpaceEventHandler: any = null; constructor(option: any) { this.id = option.id; deepMerge(this.option, option); this.config = this.formatConfig(this.option); // 创建数据源 this.dataSource = new Cesium.CustomDataSource(this.id); this.dataSource.entities.removeAll(); // 初始化热力图 this.initHeatMap(); } formatConfig(option: any) { return { id: option.id, positions: option.position, allowDrillPick: option.allowDrillPick, max: option.max, min: option.min, radius: option.radius, minOpacity: option.minOpacity, maxOpacity: option.maxOpacity, opacity: option.opacity, blur: option.blur, gradient: option.gradient || defaultOption.gradient, scaleByDistance: option.scaleByDistance, clampToGround: this.shouldClampToGround(option.position), data: option.data, }; } // 判断是否需要贴地 shouldClampToGround(position: any[]): boolean { // 如果有任何点有高度,就不贴地 if (!position || position.length === 0) return true; for (const point of position) { if (point.alt && point.alt !== 0) { return false; } } return true; } // 初始化热力图 initHeatMap() { if (!this.option.position || !Array.isArray(this.option.position)) { console.warn("热力图数据为空"); return; } // 清除现有的热力图 this.clearHeatMap(); // 计算值的范围 this.calculateValueRange(); // 创建热力图效果 this.createHeatMapPrimitive(); } // 计算值的范围 calculateValueRange() { if (this.option.min === null || this.option.max === null) { const values = this.option.position.map((p: any) => p.value || 0); this.option.min = Math.min(...values); this.option.max = Math.max(...values); // 避免除零错误 if (this.option.max === this.option.min) { this.option.max = this.option.min + 1; } } } // 创建热力图图元 createHeatMapPrimitive() { // 首先创建用于拾取的数据源实体 this.createDataSourceEntities(); // 然后创建可视化的热力图 this.createVisualHeatMap(); } // 创建数据源实体(用于交互) createDataSourceEntities() { this.option.position.forEach((point: any, index: number) => { const normalizedValue = this.normalizeValue(point.value || 0); // 构建实体属性 const entityProperties: any = { id: `${this.id}_heat_point_${index}`, position: Cesium.Cartesian3.fromDegrees( point.lng, point.lat, point.alt || 0 ), properties: { id: point.id || index, value: point.value || 0, normalizedValue: normalizedValue, ...point.data, ...this.option.data, }, }; // 添加点属性(仅用于拾取) if (this.option.allowDrillPick) { entityProperties.point = { pixelSize: 1, color: Cesium.Color.TRANSPARENT, outlineColor: Cesium.Color.TRANSPARENT, outlineWidth: 0, // 移除 heightReference 或者提供明确的高度 heightReference: point.alt ? Cesium.HeightReference.NONE : Cesium.HeightReference.CLAMP_TO_GROUND, disableDepthTestDistance: Number.POSITIVE_INFINITY, }; } const entity = this.dataSource.entities.add(entityProperties); this.entities.push(entity); }); // 将数据源添加到地图 if (hnMap.map.map && hnMap.map.map.dataSources) { let exists = false; hnMap.map.map.dataSources._dataSources.forEach((ds: any) => { if (ds.name === this.id) { exists = true; } }); if (!exists) { hnMap.map.map.dataSources.add(this.dataSource); console.log(`热力图数据源 ${this.id} 已添加到地图`); } } } // 创建可视化热力图 createVisualHeatMap() { // 使用粒子系统或点云创建热力图效果 this.createHeatMapPoints(); } // 创建热力图点 createHeatMapPoints() { // 清除现有图元 if (this.primitive && hnMap.map.map.scene) { hnMap.map.map.scene.primitives.remove(this.primitive); this.primitive = null; } // 创建点集合 const points: any[] = []; this.option.position.forEach((point: any) => { const value = point.value || 0; const normalizedValue = this.normalizeValue(value); const color = this.calculateColor(normalizedValue); const size = this.calculateSize(normalizedValue); // 创建点图元 const pointPrimitive = new Cesium.PointPrimitive({ position: Cesium.Cartesian3.fromDegrees( point.lng, point.lat, point.alt || 0 ), pixelSize: size, color: color, outlineColor: Cesium.Color.TRANSPARENT, outlineWidth: 0, scaleByDistance: this.option.scaleByDistance ? new Cesium.NearFarScalar(1.5e2, 2.0, 1.5e7, 0.5) : undefined, heightReference: point.alt ? Cesium.HeightReference.NONE : Cesium.HeightReference.CLAMP_TO_GROUND, // 默认贴地 }); points.push(pointPrimitive); }); // 创建点图元集合 if (points.length > 0) { this.primitive = new Cesium.PointPrimitiveCollection({ blendOption: Cesium.BlendOption.OPAQUE_AND_TRANSLUCENT, }); // 添加所有点到集合 points.forEach((point) => { this.primitive.add(point); }); // 添加到场景 if (hnMap.map.map.scene) { hnMap.map.map.scene.primitives.add(this.primitive); console.log(`热力图可视化 ${this.id} 已添加到场景`); } } } // 归一化值 normalizeValue(value: number): number { const range = this.option.max - this.option.min; if (range === 0) return 0; return Math.max(0, Math.min(1, (value - this.option.min) / range)); } // 计算颜色 calculateColor(value: number): any { const gradient = this.option.gradient || defaultOption.gradient; // 使用Cesium的颜色插值 if (value <= 0.4) { return this.getColorFromString(gradient["0.4"] || "blue").withAlpha( this.option.minOpacity + value * (this.option.maxOpacity - this.option.minOpacity) ); } else if (value <= 0.6) { return this.getColorFromString(gradient["0.6"] || "green").withAlpha( this.option.minOpacity + value * (this.option.maxOpacity - this.option.minOpacity) ); } else if (value <= 0.8) { return this.getColorFromString(gradient["0.8"] || "yellow").withAlpha( this.option.minOpacity + value * (this.option.maxOpacity - this.option.minOpacity) ); } else { return this.getColorFromString(gradient["0.9"] || "red").withAlpha( this.option.minOpacity + value * (this.option.maxOpacity - this.option.minOpacity) ); } } // 从字符串获取颜色 getColorFromString(colorStr: string): any { const colorMap: any = { blue: Cesium.Color.BLUE, green: Cesium.Color.GREEN, yellow: Cesium.Color.YELLOW, red: Cesium.Color.RED, cyan: Cesium.Color.CYAN, magenta: Cesium.Color.MAGENTA, white: Cesium.Color.WHITE, black: Cesium.Color.BLACK, }; if (colorMap[colorStr.toLowerCase()]) { return colorMap[colorStr.toLowerCase()]; } // 尝试解析十六进制颜色 if (colorStr.startsWith("#")) { try { return Cesium.Color.fromCssColorString(colorStr); } catch (e) { console.warn(`无法解析颜色: ${colorStr},使用默认颜色`); return Cesium.Color.BLUE; } } // 尝试解析rgb/rgba颜色 if (colorStr.includes("rgb")) { try { return Cesium.Color.fromCssColorString(colorStr); } catch (e) { console.warn(`无法解析颜色: ${colorStr},使用默认颜色`); return Cesium.Color.BLUE; } } return Cesium.Color.BLUE; } // 计算点大小 calculateSize(value: number): number { const baseSize = this.option.radius; const maxSize = baseSize * 2; return Math.max(5, baseSize + value * (maxSize - baseSize)); } // 设置热力图参数 set(option: any) { deepMerge(this.option, option); this.config = this.formatConfig(this.option); // 重新创建热力图 this.initHeatMap(); } // 销毁热力图 destroy() { this.clearHeatMap(); // 从地图数据源中移除 if (this.dataSource && hnMap.map.map && hnMap.map.map.dataSources) { try { hnMap.map.map.dataSources.remove(this.dataSource, true); } catch (error) { console.warn("移除数据源失败:", error); } } // 移除事件处理器 if (this.screenSpaceEventHandler) { this.screenSpaceEventHandler.destroy(); this.screenSpaceEventHandler = null; } } // 清除热力图 clearHeatMap() { // 清除图元 if (this.primitive && hnMap.map.map.scene) { hnMap.map.map.scene.primitives.remove(this.primitive); this.primitive = null; } // 清除几何实例 this.geometryInstances = []; // 清除数据源实体 if (this.dataSource) { this.dataSource.entities.removeAll(); } this.entities = []; } // 飞行到热力图区域 flyTo() { if ( !this.option.position || this.option.position.length === 0 || !hnMap.map.map ) { return; } const positions = this.option.position.map((point: any) => { return Cesium.Cartesian3.fromDegrees( point.lng, point.lat, point.alt || 0 ); }); if (positions.length === 1) { // 单个点 hnMap.map.map.camera.flyTo({ destination: positions[0], duration: 2.0, orientation: { heading: 0, pitch: Cesium.Math.toRadians(-45), roll: 0, }, }); } else { // 多个点,计算边界球 const boundingSphere = Cesium.BoundingSphere.fromPoints(positions); hnMap.map.map.camera.flyToBoundingSphere(boundingSphere, { duration: 2.0, offset: new Cesium.HeadingPitchRange( 0, Cesium.Math.toRadians(-45), boundingSphere.radius * 2.0 ), }); } } // 清除所有热力点 clear() { this.clearHeatMap(); } // 设置热力点位置 setPosition(position: any) { deepMerge(this.option, { position }); this.initHeatMap(); } // 显示热力图 show() { if (this.dataSource) { this.dataSource.show = true; } if (this.primitive) { this.primitive.show = true; } } // 隐藏热力图 hide() { if (this.dataSource) { this.dataSource.show = false; } if (this.primitive) { this.primitive.show = false; } } // 添加弹窗支持 addPopupByAttr() { if (!hnMap.map.map || this.screenSpaceEventHandler) { return; } this.screenSpaceEventHandler = new Cesium.ScreenSpaceEventHandler( hnMap.map.map.canvas ); this.screenSpaceEventHandler.setInputAction((movement: any) => { const pickedObject = hnMap.map.map.scene.pick(movement.position); if (pickedObject && pickedObject.id && pickedObject.id.properties) { const properties = pickedObject.id.properties; const data = {}; // 提取属性 if (typeof properties.getValue === "function") { const propertyValue = properties.getValue(Cesium.JulianDate.now()); if (propertyValue) { Object.assign(data, propertyValue); } } // 显示弹窗 this.showPopup(data, movement.position); } }, Cesium.ScreenSpaceEventType.LEFT_CLICK); } // 显示弹窗 showPopup(data: any, position: any) { console.log("热力图弹窗数据:", data); // 这里可以创建自定义弹窗 if (hnMap.map.map && hnMap.map.map.selectedEntity) { hnMap.map.map.selectedEntity = null; } } // 添加自定义弹窗 addCustomPopup(getCustomDom: Function) { this.addPopupByAttr(); } } const fn: any = { mars3d: mars3d_class, gaode: gaode_class, siji: siji_class, cesium: cesium_class, }; return fn[hnMap.mapType]; };