首页 > 其他分享 >cesium 点聚合

cesium 点聚合

时间:2024-08-02 15:57:48浏览次数:12  
标签:canvas 聚合 ctx num let cesium id size

撒点功能是gis项目中,最常用的功能。如果点位比较密集,就会出现点位图标重叠问题,如果点位过多,会影响绘制的性能,就会导致页面出现卡顿等问题。点聚合是一种比较好的解决方式。本篇对Entity图元的点聚合进行封装,在数据量不是太大的情况下,可以取得比较好的显示效果,如果数据量比较大,使用Entity图元的点聚合仍然会出现性能问题,可以通过对primitive点图元进行封装,支持更大体量的点聚合,后续primitive图元篇重再介绍。

1 EntityCluster 类

前面的篇章介绍过CustomDataSource类,它有一个属性clustering 可以完成改数据源的点聚合的功能。clustering是一个EntityCluster类对象,定义屏幕空间对象(广告牌、点、标签)如何聚集。其参数如下:

通过对EntityCluster类的聚合事件clusterEvent的监听,可以动态的设置聚合后的点样式。clusterEvent回调函数返回一个点位集合和聚合后的entity图元。使用示例如下:

dataSource.clustering.clusterEvent.addEventListener(function(entities, cluster) {
    cluster.label.show = false;
    cluster.billboard.show = true;
    cluster.billboard.image = this.getCluserCanvasImage(entities.length);
});

2 点聚合样式

根据聚合点位集合的大小,可以设置聚合点图标的大小和颜色,这里通过canvas动态绘制聚合点图标的方式,给出两种点聚合样式。如果对聚合样式有更高的要求,可以通过外部传入样式图片的方式,这里不再详细说明。

2.1 点聚合样式1
getCluserCanvasImage(num) {
		if (this.cluserCanvasImages[num]) {
			return this.cluserCanvasImages[num];
		}
		//创建cavas
		let canvas = document.createElement("canvas");
		//canvas大小
		let size = 12 * (num + "").length + 50;
		canvas.width = canvas.height = size;
		//获取canvas 上下文
		let ctx = canvas.getContext("2d");
		//绘制
		ctx.beginPath();
		ctx.globalAlpha = 0.5;
		(ctx.fillStyle = this.getCluseColor(num)),
			ctx.arc(size / 2, size / 2, size / 2 - 5, 0, 2 * Math.PI),
			ctx.fill(),
			ctx.beginPath(),
			(ctx.globalAlpha = 0.8),
			(ctx.fillStyle = this.getCluseColor(num)),
			ctx.arc(size / 2, size / 2, size / 2 - 10, 0, 2 * Math.PI),
			ctx.fill(),
			(ctx.font = "20px alpht"),
			(ctx.globalAlpha = 1),
			(ctx.fillStyle = "rgb(255,255,255)");
		let offset = size / 2 - (12 * num.toString().length) / 2;
		ctx.fillText(num, offset, size / 2 + 7);
		this.cluserCanvasImages[num] = canvas;
		return canvas;
	}
2.2 点聚合样式2
getCluserCanvasImage2(num) {
		if (this.cluserCanvasImages[num]) {
			return this.cluserCanvasImages[num];
		}
		//创建cavas
		let canvas = document.createElement("canvas");
		//canvas大小
		let size = 12 * (num + "").length + 50;
		canvas.width = canvas.height = size;
		//获取canvas 上下文
		let ctx = canvas.getContext("2d");
		//绘制
		ctx.translate(size / 2, size / 2);
		ctx.fillStyle = this.getCluseColor(num);

		const angle = Math.PI / 2;
		const dltAngle = Math.PI / 6;
		let startAngle = 0;
		let endAngle = 0;
		for (let i = 0; i < 3; i++) {
			ctx.beginPath();
			ctx.moveTo(0, 0);
			ctx.globalAlpha = 0.5;
			startAngle = endAngle + dltAngle;
			endAngle = startAngle + angle;
			ctx.arc(0, 0, size / 2 - 5, startAngle, endAngle);
			ctx.closePath();
			ctx.fill();
		}
		ctx.beginPath();
		ctx.moveTo(0, 0);
		ctx.globalAlpha = 1.0;
		ctx.arc(0, 0, size / 4, 0, 2 * Math.PI), ctx.fill();

		ctx.translate(-size / 2, -size / 2);
		ctx.font = "20px alpht";
		ctx.globalAlpha = 1;
		ctx.fillStyle = "rgb(255,255,255)";
		let offset = size / 2 - (12 * num.toString().length) / 2;
		ctx.fillText(num, offset, size / 2 + 7);
		this.cluserCanvasImages[num] = canvas;
		return canvas;
	}

3 ClusterLayer 类封装

ClusterLayer.js

/*
 * @Description:
 * @Author: maizi
 * @Date: 2022-05-27 11:36:22
 * @LastEditTime: 2024-07-23 16:48:10
 * @LastEditors: maizi
 */
const merge = require('deepmerge')
import carIcon from "@/assets/img/car-icon.png";
import selectCarIcon from "@/assets/img/car-s-icon.png";
const defaultStyle = {
	pixelRange: 40,
	minimumClusterSize: 2,
	image: carIcon,
	selectImage: selectCarIcon,
	scale: 0.7,
	type: 0,
	cluserColors: [
		{
			value: 40,
			color: "rgb(255,0,0)"
		},
		{
			value: 30,
			color: "rgb(255,0,255)"
		},
		{
			value: 20,
			color: "rgb(255,255,0)"
		},
		{
			value: 10,
			color: "rgb(0,255,255)"
		},
		{
			value: 1,
			color: "rgb(0,255,0)"
		}
	]
};
class ClusterLayer {
	constructor(options = {}) {
		this.options = options;
		this.style = merge(defaultStyle, this.options.style || {});
		this.layer = null;

		this.cluserCanvasImages = {};
		this.init();
	}
	init() {
		this.layer = new Cesium.CustomDataSource("clusterLayer");
		this.layer.clustering.enabled = true;
		this.layer.clustering.pixelRange = this.style.pixelRange;
		this.layer.clustering.minimumClusterSize = this.style.minimumClusterSize;
		this.setCanvasClusterEvent();
	}

	addDataToLayer(data) {
		data.forEach(item => {
			const entity = new Cesium.Entity({
				id: item.id ? item.id : Math.random().toString(36).substring(2),
				props: item,
				position: Cesium.Cartesian3.fromDegrees(item.coords[0], item.coords[1]),
				billboard: {
					image: this.style.image,
					scale: this.style.scale,
					disableDepthTestDistance: Number.POSITIVE_INFINITY
				}
			});
			entity.layer = this.layer;
			this.layer.entities.add(entity);
		});
	}
	
	setCanvasClusterEvent() {
		this.layer.clustering.clusterEvent.addEventListener((clusteredEntities, feature) => {
			feature.label.show = false;
			feature.layer = this.layer;
			feature.billboard.show = true;
			feature.billboard.id = feature.label.id;
			feature.billboard.verticalOrigin = Cesium.VerticalOrigin.BOTTOM;
			feature.billboard.disableDepthTestDistance = Number.POSITIVE_INFINITY;
			let image = null;
			switch (this.style.type) {
				case 0:
					image = this.getCluserCanvasImage(clusteredEntities.length);
					break;
				case 1:
					image = this.getCluserCanvasImage2(clusteredEntities.length);
					break;
			}
			feature.billboard.image = image;
		});
	}

	getCluserCanvasImage(num) {
		if (this.cluserCanvasImages[num]) {
			return this.cluserCanvasImages[num];
		}
		//创建cavas
		let canvas = document.createElement("canvas");
		//canvas大小
		let size = 12 * (num + "").length + 50;
		canvas.width = canvas.height = size;
		//获取canvas 上下文
		let ctx = canvas.getContext("2d");
		//绘制
		ctx.beginPath();
		ctx.globalAlpha = 0.5;
		(ctx.fillStyle = this.getCluseColor(num)),
			ctx.arc(size / 2, size / 2, size / 2 - 5, 0, 2 * Math.PI),
			ctx.fill(),
			ctx.beginPath(),
			(ctx.globalAlpha = 0.8),
			(ctx.fillStyle = this.getCluseColor(num)),
			ctx.arc(size / 2, size / 2, size / 2 - 10, 0, 2 * Math.PI),
			ctx.fill(),
			(ctx.font = "20px alpht"),
			(ctx.globalAlpha = 1),
			(ctx.fillStyle = "rgb(255,255,255)");
		let offset = size / 2 - (12 * num.toString().length) / 2;
		ctx.fillText(num, offset, size / 2 + 7);
		this.cluserCanvasImages[num] = canvas;
		return canvas;
	}

	getCluserCanvasImage2(num) {
		if (this.cluserCanvasImages[num]) {
			return this.cluserCanvasImages[num];
		}
		//创建cavas
		let canvas = document.createElement("canvas");
		//canvas大小
		let size = 12 * (num + "").length + 50;
		canvas.width = canvas.height = size;
		//获取canvas 上下文
		let ctx = canvas.getContext("2d");
		//绘制
		ctx.translate(size / 2, size / 2);
		ctx.fillStyle = this.getCluseColor(num);

		const angle = Math.PI / 2;
		const dltAngle = Math.PI / 6;
		let startAngle = 0;
		let endAngle = 0;
		for (let i = 0; i < 3; i++) {
			ctx.beginPath();
			ctx.moveTo(0, 0);
			ctx.globalAlpha = 0.5;
			startAngle = endAngle + dltAngle;
			endAngle = startAngle + angle;
			ctx.arc(0, 0, size / 2 - 5, startAngle, endAngle);
			ctx.closePath();
			ctx.fill();
		}
		ctx.beginPath();
		ctx.moveTo(0, 0);
		ctx.globalAlpha = 1.0;
		ctx.arc(0, 0, size / 4, 0, 2 * Math.PI), ctx.fill();

		ctx.translate(-size / 2, -size / 2);
		ctx.font = "20px alpht";
		ctx.globalAlpha = 1;
		ctx.fillStyle = "rgb(255,255,255)";
		let offset = size / 2 - (12 * num.toString().length) / 2;
		ctx.fillText(num, offset, size / 2 + 7);
		this.cluserCanvasImages[num] = canvas;
		return canvas;
	}

	getCluseColor(num) {
		for (let i = 0; i < this.style.cluserColors.length; i++) {
			let item = this.style.cluserColors[i];
			if (num >= item.value) {
				return item.color;
			}
		}
	}

	updateStyle(style) {
		this.style = merge(this.style, style);
		this.layer.clustering.pixelRange = this.style.pixelRange;
		this.layer.clustering.minimumClusterSize = this.style.minimumClusterSize;
	}

	setType(type) {
		this.style.type = type;
	}

	setSelectById(id, enabled) {
		if (this.layer) {
			const entity = this.layer.entities.getById(id);
			entity && (entity.billboard.image = enabled ? this.style.selectImage : this.style.image);
		}
	}

	getEntityById(id) {
		const entity = this.layer.entities.getById(id);
		return entity;
	}

	clear() {
		this.cluserCanvasImages = {};
		if (this.layer) {
			this.layer.entities.removeAll();
		}
	}
}

export { ClusterLayer };

4 完整示例代码

MapWorks.js

import GUI from 'lil-gui'; 
// 初始视图定位在中国
import { ClusterLayer } from './ClusterLayer'

Cesium.Camera.DEFAULT_VIEW_RECTANGLE = Cesium.Rectangle.fromDegrees(90, -20, 110, 90);

//天地图key
const key = '39673271636382067f0b0937ab9a9677'
const gui = new GUI();
const params = {
  pixelRange: 40,
  minimumClusterSize: 2
}

let viewer = null;
let clusterLayer = null
let eventHandler = null
let selectGraphic = null

function initMap(container) {
  viewer = new Cesium.Viewer(container, {
    animation: false,
    baseLayerPicker: false,
    fullscreenButton: false,
    geocoder: false,
    homeButton: false,
    infoBox: false,
    sceneModePicker: false,
    selectionIndicator: false,
    timeline: false,
    navigationHelpButton: false, 
    scene3DOnly: true,
    orderIndependentTranslucency: false,
    contextOptions: {
      webgl: {
        alpha: true
      }
    }
  })
  viewer._cesiumWidget._creditContainer.style.display = 'none'
  viewer.scene.fxaa = true
  viewer.scene.postProcessStages.fxaa.enabled = true
  if (Cesium.FeatureDetection.supportsImageRenderingPixelated()) {
    // 判断是否支持图像渲染像素化处理
    viewer.resolutionScale = window.devicePixelRatio
  }
  // 移除默认影像
  removeAll()
  // 地形深度测试
  viewer.scene.globe.depthTestAgainstTerrain = true
  // 背景色
  viewer.scene.globe.baseColor = new Cesium.Color(0.0, 0.0, 0.0, 0)
  // 太阳光照
  viewer.scene.globe.enableLighting = true;

  // 初始化图层
  initLayer()
  // 初始化鼠标事件
  initClickEvent()

   //gui面板
  initGui()

  //调试
  window.viewer = viewer
}

function initGui() {
  let layerFolder = gui.title('样式设置')
  layerFolder.add(params, 'pixelRange', 20, 200).step(1).onChange(function (value) {
    clusterLayer.updateStyle(params)
  })
  layerFolder.add(params, 'minimumClusterSize', 2, 10).step(1).onChange(function (value) {
    clusterLayer.updateStyle(params)
  })
}

function addTdtLayer(options) {
  let url = `https://t{s}.tianditu.gov.cn/DataServer?T=${options.type}&x={x}&y={y}&l={z}&tk=${key}`
  const layerProvider = new Cesium.UrlTemplateImageryProvider({
    url: url,
    subdomains: ['0','1','2','3','4','5','6','7'],
    tilingScheme: new Cesium.WebMercatorTilingScheme(),
    maximumLevel: 18
  });
  viewer.imageryLayers.addImageryProvider(layerProvider);
}

function initLayer() {
  addTdtLayer({
    type: 'img_w'
  })
  addTdtLayer({
    type: 'cia_w'
  })
  clusterLayer = new ClusterLayer()
  viewer.dataSources.add(clusterLayer.layer);
} 

function loadClusterData(data, options) {
  clusterLayer.clear()
  clusterLayer.setType(options.type)
  clusterLayer.addDataToLayer(data)
  viewer.flyTo(clusterLayer.layer);
}


function initClickEvent() {
  eventHandler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
  initLeftClickEvent()
  initMouseMoveEvent()
}

function initLeftClickEvent() {
  eventHandler.setInputAction((e) => {
    const pickedObj = viewer.scene.pick(e.position);
    if (selectGraphic) {
			clusterLayer.setSelectById(selectGraphic.id, false);
			selectGraphic = null;
		}
    if (pickedObj && pickedObj.id) {
			if (pickedObj.id instanceof Cesium.Entity) {
				//选中单个
				if (pickedObj.id.layer && pickedObj.id.layer.name === "clusterLayer") {
					selectGraphic = pickedObj.id;
					clusterLayer.setSelectById(selectGraphic.id, true);
				}
			} else if (pickedObj.id instanceof Array) {
				// 选中聚合
				if (pickedObj.id[0].layer && pickedObj.id[0].layer.name === "clusterLayer") {
					console.log("聚合点位信息=>", pickedObj.id);
				}
			}
		}
  },Cesium.ScreenSpaceEventType.LEFT_CLICK)
}

function initMouseMoveEvent() {
  eventHandler.setInputAction((e) => {
    const pickedObj = viewer.scene.pick(e.endPosition);
    if (pickedObj && pickedObj.id) {
      if (pickedObj.id instanceof Cesium.Entity || pickedObj.id instanceof Array) {
          // 改变鼠标状态
        viewer.enableCursorStyle = false;
        viewer._element.style.cursor = "";
        document.body.style.cursor = "pointer";
      }
    } else {
      // 改变鼠标状态
      viewer.enableCursorStyle = true;
      viewer._element.style.cursor = "";
      document.body.style.cursor = "default";
    }
  }, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
}


function removeAll() {
  viewer.imageryLayers.removeAll();
}

function destroy() {
  viewer.entities.removeAll();
  viewer.imageryLayers.removeAll();
  viewer.destroy();
}

export {
  initMap,
  loadClusterData,
  destroy
}

ClusterLayer.vue

<!--
 * @Description: 
 * @Author: maizi
 * @Date: 2023-04-07 17:03:50
 * @LastEditTime: 2023-04-14 09:47:03
 * @LastEditors: maizi
-->

<template>
  <div id="container">
    <div class="pane_container">
      <el-button size="small" @click="changeType(0)">聚合样式1</el-button>
      <el-button size="small" @click="changeType(1)">聚合样式2</el-button>
    </div>
  </div>
</template>

<script>
import * as MapWorks from './js/MapWorks'
export default {
  name: 'ClusterLayer',
  mounted() {
    this.init();
  },
  methods:{
    init(){
      let container = document.getElementById("container");
      MapWorks.initMap(container)
    },
    changeType(type) {
      const data = [
      {
				coords: [104.06791968990645, 30.639931966652405],
				id: "ulqg4wpkado1",
			},
			{
				coords: [104.08286762001791, 30.651738427212116],
				id: "ulqg4wpkado2",
			},
			{
				coords: [104.05749321027527, 30.65012469539701],
				id: "ulqg4wpkado3",
			},
			{
				coords: [104.08726215231565, 30.64326919178345],
				id: "ulqg4wpkado4",

			},
			{
				coords: [104.05035209594628, 30.63767262782807],
				id: "ulqg4wpkado5",
			},
			{
				coords: [104.06333112463464, 30.65974843756529],
				id: "ulqg4wpkado6",
			},
			{
				coords: [104.05830951574102, 30.67804353001073],
				id: "ulqg4wpkado7",
			},
			{
				coords: [104.05817218349625, 30.669883306472084],
				id: "ulqg4wpkado8",
			},
			{
				coords: [104.05049441213879, 30.66347415161995],
				id: "ulqg4wpkado9",
			},
			{
				coords: [104.04463502177197, 30.676841811429426],
				id: "ulqg4wpkado10",
			},
			{
				coords: [104.03804322070602, 30.66933394719844],
				id: "ulqg4wpkado11",
			},
			{
				coords: [104.09535525896746, 30.657248119145248],
				id: "ulqg4wpkado12",
			},
			{
				coords: [104.08702274136078, 30.671537092255143],
				id: "ulqg4wpkado13",
			},
			{
				coords: [104.07828569455882, 30.65974271388589],
				id: "ulqg4wpkado14",
			},
			{
				coords: [104.07607075284544, 30.677677293114513],
				id: "ulqg4wpkado15",
			},
			{
				coords: [104.07594606111917, 30.670615773715657],
				id: "ulqg4wpkado16",
			}
      ]
      MapWorks.loadClusterData(data, {
        type: type
      })
    }
  },

  beforeDestroy(){
    //实例被销毁前调用,页面关闭、路由跳转、v-if和改变key值
    MapWorks.destroy();
  }
}
</script>

<style lang="scss" scoped>
#container{
  width: 100%;
  height: 100%;
  background: rgba(7, 12, 19, 1);
  overflow: hidden;
  background-size: 40px 40px, 40px 40px;
  background-image: linear-gradient(hsla(0, 0%, 100%, 0.05) 1px, transparent 0), linear-gradient(90deg, hsla(0, 0%, 100%, 0.05) 1px, transparent 0);
  .pane_container{
    position: absolute;
    left: 10px;
    top: 50px;
    padding: 10px 15px;
    border-radius: 4px;
    border: 1px solid rgba(128, 128, 128, 0.5);
    color: #ffffff;
    background: rgba(0, 0, 0, 0.4);
    box-shadow: 0 3px 14px rgb(128 128 128 / 50%);
    z-index: 2;
  }
}


</style>

5 运行结果

标签:canvas,聚合,ctx,num,let,cesium,id,size
From: https://blog.csdn.net/hongxianqiang/article/details/140872536

相关文章

  • mapbox聚合使用自定义图标
    1mapboxgl.accessToken='YOUR_MAPBOX_ACCESS_TOKEN';2varmap=newmapboxgl.Map({3container:'map',4style:'mapbox://styles/mapbox/streets-v11',5center:[-74.5,40],6zoom:9.57});89map.on(�......
  • cesium 剖面分析
    cesium剖面分析实现思路通过采样点方式在一条线上平均分布相应的采样点,通过采样点的位置和高程数据获取每个采样点的高度,用采样点高度画海拔走势折线图;示例代码<!DOCTYPEhtml><htmllang="en"</......
  • Vue+cesium二次开发实例
    创建Vue3项目首先使用vue createvue-demo(自定义项目名)创建一个vue3项目接下来选择自定义配置,这里我们选择Router,其他几个按需选择选择vue3版本这里选择否(输入N),其他几项可以跳过,回车即可配置项目文件项目创建成功,接下来我们在VScode中打开这个文件夹,并新建一个终端......
  • Cesium加载带有背景图(包含多个背景图,图标和文字并排)的billboard
    思路:如果是简单的背景加文字,背景图会把文字挡住,又或者是背景图要有图标加文字,这时需要用canvas将背景图,图标和文字合并成一张图片,加载billboard的时候直接加载合成之后的图片即可。letcanvas=document.createElement("canvas");canvas.width=100;canvas.height=30;let......
  • Elasticsearch——聚合详解
    作者简介:大家好,我是smart哥,前中兴通讯、美团架构师,现某互联网公司CTO联系qq:184480602,加我进群,大家一起学习,一起进步,一起对抗互联网寒冬学习必须往深处挖,挖的越深,基础越扎实!阶段1、深入多线程阶段2、深入多线程设计模式阶段3、深入juc源码解析阶段4、深入jdk其余源码解析......
  • ensp链路聚合小实验
    本人网工小白,来分享学习心得,如有错误或者模糊的地方欢迎大家评论指出链路聚合技术是通过将多个物理链路捆绑为一个逻辑链路,旨在提高网络带宽和增强连接的可靠性。这种技术允许通过多个接口之间共享流量,实现负载均衡和冗余。链路聚合的两种模式:手工模式不提供链路聚合组的动......
  • 一篇文章教你如何读懂 JMeter聚合报告参数!
    在进行性能测试时,JMeter是一款备受推崇的开源工具。而其中的聚合报告(AggregateReport)是我们分析测试结果、了解系统性能的重要依据。今天,我们就来深入探讨如何读懂JMeter聚合报告中的各项参数。面对复杂的聚合报告,究竟哪些参数是我们必须关注的?这些参数背后又隐藏着怎样的重要信......
  • Cesium实战功能教程之3dtiles操作(移动+旋转)
    在平常的工作中,难免会用到倾斜摄影,当加载倾斜摄影的时候,最头疼的就是倾斜摄影的偏移问题,在代码中进行修改加载倾斜摄影的偏移参数,虽然简单但过于麻烦,也耽误开发的效率,因此我就本着能不能在三维场景中对倾斜摄影进行手动操作,无需再改代码并可将倾斜摄影放在较为正确的位置。......
  • go高并发之路——数据聚合处理
    数据聚合处理,指的是在某个请求或者脚本处理中,我们不会把这个数据立刻响应给前端或者立刻发送给下游,而是对数据先进行聚合处理一下,等到达某个阈值(时间或者量级),再响应给前端或者发送给下游。举个实际的业务场景:直播间有一个做任务的功能,用户满足购买了多少金额我们就会给该用户发放......
  • 【YOLOv8改进 - 注意力机制】ContextAggregation : 上下文聚合模块,捕捉局部和全局上下
    YOLOv8目标检测创新改进与实战案例专栏专栏目录:YOLOv8有效改进系列及项目实战目录包含卷积,主干注意力,检测头等创新机制以及各种目标检测分割项目实战案例专栏链接:YOLOv8基础解析+创新改进+实战案例介绍摘要卷积神经网络(CNNs)在计算机视觉中无处不在,具有众多高效......