import { Cesium } from '../../../namespace'
import heatmapFactory from '../heatmap/Heatmap'
/**
* 人力图分析主类
* @class
*/
class HeatMap {
/**
* 构造函数
* @param {Viewer} viewer 地图场景对象
*/
constructor(viewer) {
this._viewer = viewer
this.CustomType = 'HeatMap'
this.dom = undefined
this.id =
new Date().getTime() + '' + Number(Math.random() * 1000).toFixed(0)
this._updateId = this.id
this.canvasw = 200
this.polygon = undefined
this.primitive = undefined
this.bound = undefined // 四角坐标
this.rect = {} // 经纬度范围
this.x_axios = undefined // x 轴
this.y_axios = undefined // y 轴
this.girthX = 0 // x轴长度
this.girthY = 0 // y轴长度
this._createDom()
}
/**
* 热力图数据,支持动态设定
* @type {Array<HeatmapDatas>}
*/
get datas() {
return this._list || []
}
set datas(list) {
if (this._type === 3) return
this._initParams(list)
this._list = list
this._updateId =
new Date().getTime() + '' + Number(Math.random() * 1000).toFixed(0)
}
/**
* 热力图类型,支持设置切换,2-二维热力图,3-三维热力图
* @type {Number}
*/
get type() {
return this._type
}
set type(val) {
this._type = val
if (val === 2) {
this.primitive && this._showPrimitive(false)
this.polygon ? this._showPolygon(true) : this._drawMap(this._list)
}
if (val === 3) {
this.polygon && this._showPolygon(false)
this.primitive && this._updateId === this.id
? this._showPrimitive(true)
: this._drawPrimitive(this._list)
}
}
_showPolygon(bool) {
this.polygon.show = bool
}
_showPrimitive(bool) {
this.primitive.show = bool
}
/**
* 初始化热力图
* @param {Array<HeatmapDatas>} datas 待绘制热力图数据集
* @param {Number} [type=2] 热力图类型,2-2D热力图;3-3D热力图
* @param {HeatmapOptions} [options={}] 可选,热力图绘制参数,默认为{}
*/
initMap(datas, type = 2, options = {}) {
this._type = type
this.options = options
this._list = datas
this.heatmapInstance = this._initHeatmapInstance()
switch (this._type) {
case 2:
this._drawMap(datas)
break
case 3:
this._drawPrimitive(datas)
break
default:
break
}
}
_initHeatmapInstance() {
while (this.dom.hasChildNodes()) {
this.dom.removeChild(this.dom.firstChild)
}
const { radius, gradient, baseHeight, primitiveType } = this.options
let config = {
container: this.dom,
radius: radius || 10,
maxOpacity: 0.7,
minOpacity: 0,
blur: 0.75,
gradient: gradient || {
'.3': 'blue',
'.5': 'green',
'.7': 'yellow',
'.95': 'red',
},
}
//3D热力图
this.baseHeight = baseHeight || 0
this.primitiveType = primitiveType || 'TRIANGLES'
return heatmapFactory.create(config)
}
_computeBound(positions) {
this.rect = this._calXY(positions)
const { minLat, maxLat, minLng, maxLng } = this.rect
this.bound = {
leftTop: Cesium.Cartesian3.fromDegrees(minLng, maxLat),
leftBottom: Cesium.Cartesian3.fromDegrees(minLng, minLat),
rightTop: Cesium.Cartesian3.fromDegrees(maxLng, maxLat),
rightBottom: Cesium.Cartesian3.fromDegrees(maxLng, minLat),
}
this.x_axios = Cesium.Cartesian3.subtract(
this.bound.rightTop,
this.bound.leftTop,
new Cesium.Cartesian3()
)
this.x_axios = Cesium.Cartesian3.normalize(
this.x_axios,
new Cesium.Cartesian3()
)
this.y_axios = Cesium.Cartesian3.subtract(
this.bound.leftBottom,
this.bound.leftTop,
new Cesium.Cartesian3()
)
this.y_axios = Cesium.Cartesian3.normalize(
this.y_axios,
new Cesium.Cartesian3()
)
this.girthX = Cesium.Cartesian3.distance(
this.bound.rightTop,
this.bound.leftTop
)
this.girthY = Cesium.Cartesian3.distance(
this.bound.leftBottom,
this.bound.leftTop
)
}
_initParams(list) {
const hierarchy = []
// 添加标签验证
for (let ind = 0; ind < list.length; ind++) {
let position = Cesium.Cartesian3.fromDegrees(
list[ind].lnglat[0],
list[ind].lnglat[1]
)
hierarchy.push(position)
}
const points =
this._type === 2
? this._calPoints2D(hierarchy, list)
: this._calPoints3D(hierarchy, list)
this.hierarchy = hierarchy
this.heatmapInstance = this._initHeatmapInstance()
this.heatmapInstance.addData(points)
}
_calPoints3D(hierarchy, list) {
this._computeBound(hierarchy)
let points = []
for (let i = 0; i < hierarchy.length; i++) {
let p1 = hierarchy[i]
const rete = this._computeRateInBound(p1)
rete &&
points.push({
x: rete.x,
y: rete.y,
value: list[i].value,
})
}
return points
}
_calPoints2D(hierarchy, list) {
const sw = this.canvasw
const bound = this._getBound(hierarchy)
if (!bound) return
const { leftTop, leftBottom, rightTop } = bound
let points = []
let x_axios = Cesium.Cartesian3.subtract(
rightTop,
leftTop,
new Cesium.Cartesian3()
)
x_axios = Cesium.Cartesian3.normalize(x_axios, new Cesium.Cartesian3())
let y_axios = Cesium.Cartesian3.subtract(
leftBottom,
leftTop,
new Cesium.Cartesian3()
)
y_axios = Cesium.Cartesian3.normalize(y_axios, new Cesium.Cartesian3())
const girthX = Cesium.Cartesian3.distance(rightTop, leftTop)
const girthY = Cesium.Cartesian3.distance(leftBottom, leftTop)
for (let i = 0; i < hierarchy.length; i++) {
const p1 = hierarchy[i]
const p_origin = Cesium.Cartesian3.subtract(
p1,
leftTop,
new Cesium.Cartesian3()
)
const diffX = Cesium.Cartesian3.dot(p_origin, x_axios)
const diffY = Cesium.Cartesian3.dot(p_origin, y_axios)
points.push({
x: Number((diffX / girthX) * sw).toFixed(0),
y: Number((diffY / girthY) * sw).toFixed(0),
value: list[i].value,
})
}
return points
}
_drawMap(list) {
this._initParams(list)
this._createPolygon()
}
_drawPrimitive(list) {
this._initParams(list)
this._createPrimitive()
}
_createPrimitive() {
this.primitive && this._viewer.scene.primitives.remove(this.primitive)
this.primitive = undefined
let instance = new Cesium.GeometryInstance({
geometry: this._createGeometry(),
})
this.primitive = this._viewer.scene.primitives.add(
new Cesium.Primitive({
geometryInstances: instance,
appearance: new Cesium.MaterialAppearance({
material: new Cesium.Material({
fabric: {
type: 'Image',
uniforms: {
image: this.heatmapInstance.getDataURL(),
},
},
}),
translucent: true,
flat: true,
}),
asynchronous: false,
})
)
this.primitive.id = 'DEJA_VU3D_HEATMAP3D'
this._updateId = this.id
}
// 计算当前坐标在范围中位置 换算为canvas中的像素坐标
_computeRateInBound(position) {
if (!position) return
let ctgc = Cesium.Cartographic.fromCartesian(position.clone())
ctgc.height = 0
position = Cesium.Cartographic.toCartesian(ctgc.clone())
const p_origin = Cesium.Cartesian3.subtract(
position.clone(),
this.bound.leftTop,
new Cesium.Cartesian3()
)
const diffX = Cesium.Cartesian3.dot(p_origin, this.x_axios)
const diffY = Cesium.Cartesian3.dot(p_origin, this.y_axios)
return {
x: Math.round(Number((diffX / this.girthX) * this.canvasw)),
y: Math.round(Number((diffY / this.girthY) * this.canvasw)),
}
}
_createGeometry() {
let opt = this._getGrain()
let geometry = new Cesium.Geometry({
attributes: new Cesium.GeometryAttributes({
position: new Cesium.GeometryAttribute({
componentDatatype: Cesium.ComponentDatatype.DOUBLE,
componentsPerAttribute: 3,
values: opt.positions,
}),
st: new Cesium.GeometryAttribute({
componentDatatype: Cesium.ComponentDatatype.FLOAT,
componentsPerAttribute: 2,
values: new Float32Array(opt.st),
}),
}),
indices: new Uint16Array(opt.indices),
primitiveType: Cesium.PrimitiveType[this.primitiveType],
boundingSphere: Cesium.BoundingSphere.fromVertices(opt.positions),
})
return geometry
}
// 根据经纬度跨度和canvas的宽高 来计算顶点坐标及顶点法向量
_getGrain() {
let canvasw = this.canvasw
let canvasH = this.canvasw
let { maxLng, maxLat, minLng, minLat } = this.rect
const granLng_w = (maxLng - minLng) / canvasw // 经度粒度
const granLat_H = (maxLat - minLat) / canvasH // 经度粒度
let positions = []
let st = []
let indices = []
for (let i = 0; i < canvasw; i++) {
let nowLng = minLng + granLng_w * i
for (let j = 0; j < canvasH; j++) {
let nowLat = minLat + granLat_H * j
const value = this.heatmapInstance.getValueAt({
x: i,
y: j,
})
let cartesian3 = Cesium.Cartesian3.fromDegrees(
nowLng,
nowLat,
this.baseHeight + value
)
positions.push(cartesian3.x, cartesian3.y, cartesian3.z)
st.push(i / canvasw, j / canvasH)
if (j != canvasH - 1 && i != canvasw - 1) {
indices.push(
i * canvasH + j,
i * canvasH + j + 1,
(i + 1) * canvasH + j
)
indices.push(
(i + 1) * canvasH + j,
(i + 1) * canvasH + j + 1,
i * canvasH + j + 1
)
}
}
}
return {
positions: positions,
st: st,
indices: indices,
}
}
_createPolygon() {
this.polygon = this._viewer.entities.add({
polygon: {
hierarchy: new Cesium.CallbackProperty(() => {
const bound = this._getBound(this.hierarchy)
const { leftTop, leftBottom, rightBottom, rightTop } = bound
return new Cesium.PolygonHierarchy([
leftTop,
leftBottom,
rightBottom,
rightTop,
])
}, false),
material: new Cesium.ImageMaterialProperty({
image: new Cesium.CallbackProperty(() => {
return this.heatmapInstance.getDataURL()
}, false),
}),
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
},
})
}
_calXY(positions) {
let rect = this._toRectangle(positions) // 转为正方形
let lnglats = cartesiansToLnglats(rect)
let minLat = Number.MAX_VALUE,
maxLat = Number.MIN_VALUE,
minLng = Number.MAX_VALUE,
maxLng = Number.MIN_VALUE
const length = rect.length
for (let i = 0; i < length; i++) {
const lnglat = lnglats[i]
if (lnglat[0] < minLng) {
minLng = lnglat[0]
}
if (lnglat[0] > maxLng) {
maxLng = lnglat[0]
}
if (lnglat[1] < minLat) {
minLat = lnglat[1]
}
if (lnglat[1] > maxLat) {
maxLat = lnglat[1]
}
}
const diff_lat = maxLat - minLat
const diff_lng = maxLng - minLng
minLat = minLat - diff_lat / length
maxLat = maxLat + diff_lat / length
minLng = minLng - diff_lng / length
maxLng = maxLng + diff_lng / length
return { minLat, maxLat, minLng, maxLng }
}
// 扩展边界 防止出现热力图被分割
_getBound(positions) {
const { minLat, maxLat, minLng, maxLng } = this._calXY(positions)
return {
leftTop: Cesium.Cartesian3.fromDegrees(minLng, maxLat),
leftBottom: Cesium.Cartesian3.fromDegrees(minLng, minLat),
rightTop: Cesium.Cartesian3.fromDegrees(maxLng, maxLat),
rightBottom: Cesium.Cartesian3.fromDegrees(maxLng, minLat),
}
}
// 任何图形均转化为正方形
_toRectangle(hierarchy) {
if (!hierarchy) return
let boundingSphere = Cesium.BoundingSphere.fromPoints(
hierarchy,
new Cesium.BoundingSphere()
)
let center = boundingSphere.center
const radius = boundingSphere.radius
let modelMatrix = Cesium.Transforms.eastNorthUpToFixedFrame(center.clone())
let roate_y = new Cesium.Cartesian3(0, 1, 0)
let arr = []
for (let i = 45; i <= 360; i += 90) {
let roateZ_mtx = Cesium.Matrix3.fromRotationZ(
Cesium.Math.toRadians(i),
new Cesium.Matrix3()
)
let yaix_roate = Cesium.Matrix3.multiplyByVector(
roateZ_mtx,
roate_y,
new Cesium.Cartesian3()
)
yaix_roate = Cesium.Cartesian3.normalize(
yaix_roate,
new Cesium.Cartesian3()
)
let third = Cesium.Cartesian3.multiplyByScalar(
yaix_roate,
radius,
new Cesium.Cartesian3()
)
let poi = Cesium.Matrix4.multiplyByPoint(
modelMatrix,
third.clone(),
new Cesium.Cartesian3()
)
arr.push(poi)
}
return arr
}
_createDom() {
this.dom = window.document.createElement('div')
this.dom.id = `deja_vu3d-heatmap-${this.id}`
this.dom.className = `deja_vu3d-heatmap`
this.dom.style.width = this.canvasw + 'px'
this.dom.style.height = this.canvasw + 'px'
this.dom.style.position = 'absolute'
this.dom.style.display = 'none'
this.dom.style.zIndex = '-99'
this._viewer.container.appendChild(this.dom)
}
/**
* 注销热力图对象
*/
destroy() {
this.dom && this.dom.remove()
this.dom = undefined
this.polygon && this._viewer.entities.remove(this.polygon)
this.polygon = undefined
this.primitive && this._viewer.scene.primitives.remove(this.primitive)
this.primitive = undefined
this.list = []
this.heatmapInstance = undefined
}
}
function cartesiansToLnglats(cartesians) {
if (!cartesians || cartesians.length < 1) return
let arr = []
for (let i = 0; i < cartesians.length; i++) {
const car3 = cartesians[i]
let lnglat = Cesium.Cartographic.fromCartesian(car3)
let lat = Cesium.Math.toDegrees(lnglat.latitude)
let lng = Cesium.Math.toDegrees(lnglat.longitude)
let hei = lnglat.height
arr.push([lng, lat, hei])
}
return arr
}
export default HeatMap