@ciniki/iclient-maplibregl
Version:
@supermapgis/iclient-maplibregl 是一套基于 Maplibre GL 的云 GIS 网络客户端开发平台, 支持访问 SuperMap iServer / iEdge / iPortal / iManager / Online 的地图、服务和资源,为用户提供了完整专业的 GIS 能力, 同时提供了优秀的可视化功能。
278 lines (257 loc) • 12 kB
JavaScript
/* Copyright© 2000 - 2025 SuperMap Software Co.Ltd. All rights reserved.
* This program are made available under the terms of the Apache License, Version 2.0
* which accompanies this distribution and is available at http://www.apache.org/licenses/LICENSE-2.0.html.*/
import maplibregl from 'maplibre-gl';
import { Util as CommonUtil } from '@ciniki/iclient-common/commontypes/Util';
import { HeatMapLayerRenderer } from '@ciniki/iclient-common/overlay/heatmap/HeatMapLayerRenderer';
/**
* @class HeatMapLayer
* @classdesc 热力图层类。热力图是通过颜色分布,描述诸如人群分布、密度和变化趋势等的一种地图表现手法。
* 热点图的衰减是像素级别的,视觉效果极佳,但不能与具体数据进行一一对应,只能表示权重之间的差别,
* 因此可以用于一些对精度要求不高而需要重点突出权重渐变的行业,如制作气象温度对比动态效果图、地震区域的震点强弱图等。<br>
* 在客户端直接渲染栅格图的三要素:<br>
* 1.热点数据,热点数据需要点数据,每一个热点数据需要有地理位置以及权重值 (能够明显地表现某位置某事件发生频率或事物分布密度等,如可以为温度的高低、人口密集度等等);<br>
* 2.热点衰减渐变填充色集合, 用于渲染每一个热点从中心向外衰减时的渐变色;<br>
* 3.热点半径,也就是衰减半径。每一个热点需要从中心点外四周根据半径计算衰减度,对在热点衰减区内的每一个像素计算需要渲染的颜色值。
* @category Visualization HeatMap
* @version 11.1.0
* @modulecategory Overlay
* @param {string} name - 图层名称。
* @param {Object} options - 构造参数。
* @param {maplibregl.Map} options.map - MapLibreGL Map 对象。
* @param {string} options.featureWeight - 对应 feature 属性中的热点权重字段名称,权重值类型为 float。
* @param {string} [options.id] - 专题图层 ID。默认使用 CommonUtil.createUniqueID("HeatMapLayer_") 创建专题图层 ID。
* @param {number} [options.radius=50] - 热点渲染的最大半径(热点像素半径),单位为 px,当 useGeoUnit 参数 为 true 时,单位使用当前图层地理坐标单位。热点显示的时候以精确点为中心点开始往四周辐射衰减,其衰减半径和权重值成比列。
* @param {boolean} [options.loadWhileAnimating=true] - 是否实时重绘。(当绘制大数据量要素的情况下会出现卡顿,建议把该参数设为 false)。
* @param {number} [options.opacity=1] - 图层不透明度。
* @param {Array.<string>} [options.colors=['blue','cyan','lime','yellow','red']] - 颜色线性渐变数组,颜色值必须为 canvas 所支持的。
* @param {boolean} [options.useGeoUnit=false] - 使用地理单位,即默认热点半径默认使用像素单位。当设置为 true 时,热点半径和图层地理坐标保持一致。
* @extends {maplibregl.Evented}
* @fires HeatMapLayer#featuresadded
* @fires HeatMapLayer#changelayer
* @fires HeatMapLayer#featuresremoved
* @usage
*/
export class HeatMapLayer extends maplibregl.Evented {
constructor(name, options) {
super();
var _options = options ? options : {};
this.options = _options;
/**
* @member {string} HeatMapLayer.prototype.name
* @description 图层名字。
*/
this.name = name;
/**
* @member {string} HeatMapLayer.prototype.id
* @description 热力图图层 ID。
*/
this.id = _options.id ? _options.id : CommonUtil.createUniqueID("HeatMapLayer_");
/**
* @member {boolean} [HeatMapLayer.prototype.loadWhileAnimating=true]
* @description 是否实时重绘。(当绘制大数据量要素的情况下会出现卡顿,建议把该参数设为false)。
*/
this.loadWhileAnimating = _options.loadWhileAnimating === undefined ? true : _options.loadWhileAnimating;
/**
* @member {Array.<FeatureVector>} HeatMapLayer.prototype.features
* @description 热点信息数组,记录存储图层上添加的所有热点信息。
*/
this.features = {};
/**
* @member HeatMapLayer.prototype.EVENT_TYPES
* @description 监听一个自定义事件可用如下方式:
* 热点图自定义事件信息,事件调用时的属性与具体事件类型相对应。
*
* 支持的事件如下 (另外包含 <Layer 中定义的其他事件>):<br>
* featuresadded - 热点添加完成时触发。回调参数为添加的热点信息数组和操作成功与否信息。
* 参数类型:{features: features, succeed: succeed}
* featuresremoved - 热点被删除时触发。回调参数为删除的热点信息数组和操作成功与否信息。<br>
* 参数类型:{features: features, succeed: succeed}
* featuresdrawcompleted - 热点图渲染完成时触发。
*/
this.EVENT_TYPES = ["featuresadded", "featuresremoved", "featuresdrawcompleted"];
this.type = 'custom';
this.renderingMode = '3d';
this.overlay = true;
}
/**
* @function HeatMapLayer.prototype.onAdd
* @description 添加该图层
*/
onAdd(map) {
this.map = map;
const targetElement = this.map.getCanvasContainer();
const mapElement = this.map.getCanvas();
this.renderer = new HeatMapLayerRenderer({ id: this.id, ...this.options, convertLatlonToPixel: this._convertLatlonToPixel.bind(this), targetElement, mapElement });
if (this.features.features && this.features.features.length) {
this.renderer.setExtent(this.map.getBounds());
this.renderer.addFeatures(this.features);
}
}
/**
* @function HeatMapLayer.prototype.onRemove
* @description 移除该图层
*/
onRemove() {
this.removeAllFeatures();
this.renderer.removeFromMap();
this.features = {};
this.renderer = null;
}
/**
* @function HeatMapLayer.prototype.render
* @description 渲染图层
*/
render() {
this.refresh();
}
/**
* @function HeatMapLayer.prototype.addFeatures
* @description 添加热点信息。
* @param {GeoJSONObject} features - 待添加的要素数组。
*
* @example
* var geojson = {
* "type": "FeatureCollection",
* "features": [
* {
* "type": "feature",
* "geometry": {
* "type": "Point", //只支持point类型
* "coordinates": [0, 0]
* },
* "properties": {
* "height": Math.random()*9,
* "geoRadius": useGeoRadius?radius:null
* }
* }
* ]
* };
* var heatMapLayer = new HeatMapLayer("heatmaplayer",{"featureWeight":"height"}); pLayer = new HeatMapLayer("heatmaplayer",{"featureWeight":"height"});
* heatMapLayer.addFeatures(geojson);
* map.addLayer(heatMapLayer);
*/
addFeatures(features) {
if (this.renderer) {
this.renderer.addFeatures(features);
} else {
this.features = features;
}
/**
* @event HeatMapLayer#featuresadded
* @description 要素添加完成之后触发。
* @property {GeoJSONObject} features - 被添加的要素。
* @property {boolean} succeed - 要素是否成功添加。
*/
this.fire(this.EVENT_TYPES[0], { features: features, succeed: true });
}
/**
* @function HeatMapLayer.prototype.refresh
* @description 强制刷新当前热点显示,在图层热点数组发生变化后调用,更新显示。
*/
refresh() {
if (this.map) {
this.renderer.setExtent(this.map.getBounds());
this.renderer.refresh();
}
}
/**
* @function HeatMapLayer.prototype.setOpacity
* @description 设置图层的不透明度,取值范围:[0-1]。
* @param {number} [opacity] - 不透明度。
*/
setOpacity(opacity) {
if (opacity !== this.opacity) {
this.renderer.setOpacity(opacity);
if (this.map !== null) {
/**
* @event HeatMapLayer#changelayer
* @description 图层属性改变之后触发。
* @property {Object} layer - 图层。
* @property {string} property - 被改变的图层属性。
*/
this.fire('changelayer', { layer: this, property: "opacity" });
}
}
}
/**
* @function HeatMapLayer.prototype.updateHeatPoints
* @description 刷新热点图显示。
* @param {maplibregl.LngLatBounds} bounds - 当前显示范围。
*/
updateHeatPoints(bounds) {
this.renderer.updateHeatPoints(bounds);
}
/**
* @function HeatMapLayer.prototype.getPixelXY
* @description 转换地理坐标为相对于当前窗口左上角的像素坐标。
* @param {number} x - 热点的像素 x 坐标。
* @param {number} y - 热点的像素 y 坐标。
*/
getPixelXY(coordinate) {
return this.renderer.getPixelXY(coordinate);
}
/**
* @function HeatMapLayer.prototype.removeFeatures
* @description 移除指定的热点信息。
* @param {Array.<FeatureVector>|FeatureVector} features - 热点信息数组。
*/
removeFeatures(features) {
const removeFeaturesRes = this.renderer.removeFeatures(features);
if(!removeFeaturesRes) {
return;
}
const { heatPointsFailedRemoved, succeed } = removeFeaturesRes;
//派发删除features成功的事件
/**
* @event HeatMapLayer#featuresremoved
* @description 要素删除之后触发。
* @property {Array.<FeatureVector>} features - 需要被删除的要素。
* @property {boolean} succeed - 要素删除成功与否。
*/
this.fire(this.EVENT_TYPES[1], { features: heatPointsFailedRemoved, succeed });
}
/**
* @function HeatMapLayer.prototype.removeAllFeatures
* @description 移除全部的热点信息。
*/
removeAllFeatures() {
this.renderer.removeAllFeatures();
}
/**
* @function HeatMapLayer.prototype.moveTo
* @description 将图层移动到某个图层之前。
* @param {string} layerID - 待插入的图层ID。
* @param {boolean} [before=true] - 是否将本图层插入到图层 ID 为 layerID 的图层之前(如果为 false 则将本图层插入到图层 ID 为 layerID 的图层之后)。
*/
moveTo(layerID, before) {
var layer = document.getElementById(this.renderer.rootCanvas.id);
before = before !== undefined ? before : true;
if (before) {
var beforeLayer = document.getElementById(layerID);
if (layer && beforeLayer) {
beforeLayer.parentNode.insertBefore(layer, beforeLayer);
}
return;
}
var nextLayer = document.getElementById(layerID);
if (layer) {
if (nextLayer.nextSibling) {
nextLayer.parentNode.insertBefore(layer, nextLayer.nextSibling);
return;
}
nextLayer.parentNode.appendChild(layer);
}
}
/**
* @function HeatMapLayer.prototype.setVisibility
* @description 设置图层可见性。
* @param {boolean} [visibility] - 是否显示图层(当前地图的 resolution 在最大最小 resolution 之间)。
*/
setVisibility(visibility) {
this.renderer.setVisibility(visibility);
}
_convertLatlonToPixel(coordinate) {
return this.map.project(new maplibregl.LngLat(coordinate.lon, coordinate.lat));
}
}