Browse Source

improve the cluster layer

tags/3.0.0
Caven Chen 2 years ago
parent
commit
5687c8d5a1
1 changed files with 154 additions and 65 deletions
  1. 154
    65
      src/modules/layer/type/ClusterLayer.js

+ 154
- 65
src/modules/layer/type/ClusterLayer.js View File

@@ -2,39 +2,52 @@
* @Author : Caven Chen
*/

import Supercluster from 'supercluster'
import { Cesium } from '../../../namespace'
import State from '../../state/State'
import Layer from '../Layer'
import Parse from '../../parse/Parse'

const DEF_OPT = {
size: 18,
pixelRange: 40,
gradient: {
radius: 60,
maxZoom: 25,
style: 'circle',
image: '',
gradientColors: {
0.0001: Cesium.Color.DEEPSKYBLUE,
0.001: Cesium.Color.GREEN,
0.01: Cesium.Color.ORANGE,
0.1: Cesium.Color.RED,
},
gradientImages: {},
showCount: true,
fontSize: 12,
clusterSize: 16,
fontColor: Cesium.Color.BLACK,
style: 'circle',
getCountOffset: (count) => {
return {
x: -3.542857 * String(count).length + 1.066667,
y: String(count).length > 3 ? 5 : 4,
}
},
}

class ClusterLayer extends Layer {
constructor(id, options = {}) {
super(id)
this._delegate = new Cesium.CustomDataSource(id)
this._delegate = new Cesium.PrimitiveCollection()
this._options = {
...DEF_OPT,
...options,
}
this._delegate.clustering.enabled = true
this._delegate.clustering.clusterEvent.addEventListener(
this._clusterEventHandler,
this
)
this._delegate.clustering.pixelRange = this._options.pixelRange

this._billboards = this._delegate.add(new Cesium.BillboardCollection())
this._labels = this._delegate.add(new Cesium.LabelCollection())
this._cluster = new Supercluster({
radius: this._options.radius,
maxZoom: this._options.maxZoom,
})
this._allCount = 0
this._changedRemoveCallback = undefined
this._state = State.INITIALIZED
}

@@ -42,20 +55,19 @@ class ClusterLayer extends Layer {
return Layer.getLayerType('cluster')
}

set enableCluster(enableCluster) {
this._delegate.clustering.enabled = enableCluster
}
_addOverlay(overlay) {}
_removeOverlay(overlay) {}

/**
*
* @param color
* @param numLength
* @param count
* @returns {*}
* @private
*/
_drawCircle(color, numLength) {
let size = this._options.size * (numLength + 1)
let key = color.toCssColorString() + '-' + size
_getCircleImage(color, count) {
let size = this._options.clusterSize * (String(count).length + 1)
let key = color.toCssColorString() + '-' + count
if (!this._cache[key]) {
let canvas = document.createElement('canvas')
canvas.width = size
@@ -71,8 +83,8 @@ class ClusterLayer extends Layer {
context2D.beginPath()
context2D.arc(12, 12, 6, 0, 2 * Math.PI)
context2D.fillStyle = color.toCssColorString()
context2D.fill()
context2D.closePath()
context2D.fill()
context2D.restore()
this._cache[key] = canvas.toDataURL()
}
@@ -82,13 +94,13 @@ class ClusterLayer extends Layer {
/**
*
* @param color
* @param numLength
* @param count
* @returns {*}
* @private
*/
_drawClustering(color, numLength) {
let size = this._options.size * (numLength + 1)
let key = color.toCssColorString() + '-' + size
_getClusteringImage(color, count) {
let size = this._options.clusterSize * (String(count).length + 1)
let key = color.toCssColorString() + '-' + count
let startAngle = -Math.PI / 12
let angle = Math.PI / 2
let intervalAngle = Math.PI / 6
@@ -122,56 +134,133 @@ class ClusterLayer extends Layer {
return this._cache[key]
}

_getClusterImage(count) {
let rate = count / this._allCount
let image = undefined
if (this._options.style === 'custom') {
let keys = Object.keys(this._options.gradientImages).sort(
(a, b) => Number(a) - Number(b)
)
for (let i = keys.length - 1; i >= 0; i--) {
if (rate >= Number(keys[i])) {
image = this._options.gradientImages[keys[i]]
break
}
}
if (!image) {
image = this._options.gradientImages[keys[0]]
}
} else {
let keys = Object.keys(this._options.gradientColors).sort(
(a, b) => Number(a) - Number(b)
)
let color = undefined
for (let i = keys.length - 1; i >= 0; i--) {
if (rate >= Number(keys[i])) {
color = this._options.gradientColors[keys[i]]
break
}
}
if (!color) {
color = this._options.gradientColors[keys[0]]
}
image =
this._options.style === 'circle'
? this._getCircleImage(color, count)
: this._getClusteringImage(color, count)
}
return image
}

_changeCluster() {
this._billboards.removeAll()
this._labels.removeAll()
let rectangle = this._viewer.camera.computeViewRectangle()
if (this._allCount) {
let result = this._cluster.getClusters(
[
Cesium.Math.toDegrees(rectangle.west),
Cesium.Math.toDegrees(rectangle.south),
Cesium.Math.toDegrees(rectangle.east),
Cesium.Math.toDegrees(rectangle.north),
],
this._viewer.zoom
)
result.forEach((item) => {
if (item.properties) {
let count = item.properties.point_count
this._billboards.add({
position: Cesium.Cartesian3.fromDegrees(
+item.geometry.coordinates[0],
+item.geometry.coordinates[1]
),
image: this._getClusterImage(count),
})
if (this._options.showCount) {
this._labels.add({
position: Cesium.Cartesian3.fromDegrees(
+item.geometry.coordinates[0],
+item.geometry.coordinates[1]
),
text: String(count),
font: `${this._options.fontSize} px sans-serif`,
disableDepthTestDistance: Number.POSITIVE_INFINITY,
fillColor: this._options.fontColor,
scale: 0.8,
pixelOffset: this._options.getCountOffset(count),
})
}
} else {
this._billboards.add({
position: Cesium.Cartesian3.fromDegrees(
+item.geometry.coordinates[0],
+item.geometry.coordinates[1]
),
image: this._options.image,
})
}
})
}
}

_addedHook() {
this._changedRemoveCallback = this._viewer.camera.changed.addEventListener(
this._changeCluster,
this
)
}

_removedHook() {
this._changedRemoveCallback && this._changedRemoveCallback()
}

/**
*
* @param {*} clusteredEntities
* @param {*} cluster
* @param points
* @returns {ClusterLayer}
*/
_clusterEventHandler(clusteredEntities, cluster) {
if (!this._delegate.clustering.enabled) {
return
}
cluster.billboard.show = true
cluster.label.font = `bold ${this._options.fontSize}px sans-serif`
cluster.label.fillColor = this._options.fontColor
cluster.label.disableDepthTestDistance = Number.POSITIVE_INFINITY
if (this._delegate.entities.values.length) {
let allCount = this._delegate.entities.values.length || 0
for (let key in this._options.gradient) {
if (clusteredEntities.length >= allCount * key) {
let numLength = String(clusteredEntities.length).length
if (this._options.style === 'circle') {
cluster.billboard.image = this._drawCircle(
this._options.gradient[key],
numLength
)
} else if (this._options.style === 'custom') {
cluster.billboard.image = this._options.gradient[key]
} else {
cluster.billboard.image = this._drawClustering(
this._options.gradient[key],
numLength
)
setPoints(points = []) {
if (points.length) {
this._allCount = points.length
this._cluster.load(
points.map((item) => {
let position = Parse.parsePosition(item)
return {
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [position.lng, position.lat],
},
}
cluster.label.show = true
if (numLength === 1) {
cluster.label.pixelOffset = new Cesium.Cartesian2(-2, 3)
} else {
cluster.label.pixelOffset = new Cesium.Cartesian2(
-5 * (numLength - 1),
5
)
}
} else if (clusteredEntities.length <= 1) {
cluster.label.show = false
}
}
})
)
}
return this
}

clear() {
this._delegate.entities.removeAll()
this._cache = {}
this._allCount = 0
this._state = State.CLEARED
return this
}

Loading…
Cancel
Save