cesium实体创建与拖拽
引言:这篇文件主要介绍cesium的实体创建和拖拽,主要是部分entity实体的添加cesium场景中。常见的需求比如多边形框选、广告牌标志、文字标识、管道等等。因为实体的特性,代码主要实现实体的加载和拖拽。(官方示例)
Entity实体介绍
Entity是Cesium中的一个核心类,主要用来描述地理空间场景中的动态对象(如点、线、面、模型、标签等)。这个动态对象通常具有位置,方向,类型等属性,这些属性都是可选的,可以根据需要进行加载和调整。(API查阅点击跳转)
- 核心功能:
- 统一数据容器:
Entity
封装了对象的 位置、方向、形状、外观 等属性,并支持随时间变化的动态数据(通过Property
系统)。 - 跨数据源支持: 可用于
GeoJSON
、KML
、CZML
等数据格式,也可手动创建。
- 统一数据容器:
- 常用属性:
id
:唯一标识符(字符串或对象)position
:位置(PositionProperty
)orientation
:方向(四元数)billboard
、point
、label
、polyline
、polygon
、model
等图形类型(通过XXXGraphics
类配置)availability
:时间范围(TimeIntervalCollection
)description
:HTML描述(点击时显示在InfoBox中)
- 相关核心类
-
Graphics 类族(可视化样式): 主要定义Entity具体的图形表现(如颜色、大小、轮廓等)。常见子类如下:
BillboaLabelGraphics
:文本标签PointGraphics
:点PolylineGraphics
:折线PolygonGraphics
:多边形ModelGraphics
:3D模型(glTF格式)EllipseGraphics/BoxGraphics
:几何体rdGraphics:图标(2D图像)
-
Property 系统(动态属性): 处理随时间变化的属性(如移动轨迹、颜色渐变)。
ConstantProperty
:静态值SampledProperty
:插值数据(如位置采样)TimeIntervalCollectionProperty
:分段定义不同时间段的值CompositeProperty
:组合多个Property
-
EntityCollection 类: 管理一组Entity,通常挂载在Viewer.entities或DataSource.entities上。核心方法如下:
entities.add(entity);
:添加实体entities.remove(entity);
:删除实体entities.getById('id');
:按ID查询
-
DataSource 类: 加载外部数据(如CZML、GeoJSON),自动生成
Entity
集合。常见子类如下:CzmlDataSource
GeoJsonDataSource
KmlDataSource
-
EntityCluster 类: 当实体过多时自动聚合成图标,优化性能。
- 启用方式:
viewer.dataSourceDisplay.defaultDataSource.entities.clustering.enabled = true;
- 启用方式:
-
Entity实体创建
关于单个entity实体创建的代码我就不一一列举了,可以点击文章开头的官方示例链接进行查看。这里我主要实现的是一些entity实体创建和拖拽移动,这里说一下实现思路:
- 事件流程: 监听鼠标左键按下(LEFT_DOWN)→ 移动(MOUSE_MOVE)→ 释放(LEFT_UP)事件链。
- 位置更新: 拖拽时实时计算鼠标在三维场景中的坐标(Cartesian3),并更新Entity的position属性。
- 防干扰处理: 拖拽时禁用相机旋转/平移,避免地图移动干扰实体操作。
实现代码:
js
<script setup>
import * as cesium from "cesium";
import { onMounted, ref } from "vue";
import { useCesium } from "@/hooks/useCesium";
import logo from '../assets/img/cesium_logo.png'
import { ElMessage } from "element-plus";
let viewer = null
let handler = null
let dragEntity = null
let isDraging = false
let originalPositions = []; // 存储原始顶点坐标
/**
* StripeMaterialProperty:一个将条纹材质属性映射到材质uniforms的材质属性类
* 参数:
* orientation:指定条纹方向的属性,可选水平或垂直。
* evenColor:指定第一种(偶数条纹)颜色的属性。
* oddColor:指定第一种(偶数条纹)颜色的属性。
* offset:指定材质模式起始点的偏移量(0.0从偶数颜色开始,1.0从奇数颜色开始)。
* repeat:指定条纹重复次数的数值属性。
*/
const stripeMaterial = new cesium.StripeMaterialProperty({
evenColor: cesium.Color.WHITE.withAlpha(0.5),
oddColor: cesium.Color.BLUE.withAlpha(0.5),
repeat: 10.0,
});
// 折线体计算
const starPositions = (arms, rOuter, rInner) => {
const angle = Math.PI / arms;
const pos = [];
for (let i = 0; i < 2 * arms; i++) {
const r = i % 2 === 0 ? rOuter : rInner;
const p = new cesium.Cartesian2(
Math.cos(i * angle) * r,
Math.sin(i * angle) * r,
);
pos.push(p);
}
return pos;
}
// 折线体计算
const computeCircle = (radius) => {
const positions = [];
for (let i = 0; i < 360; i++) {
const radians = cesium.Math.toRadians(i);
positions.push(
new cesium.Cartesian2(
radius * Math.cos(radians),
radius * Math.sin(radians),
),
);
}
return positions;
}
const clickBtn = (item) => {
const entity = viewer.entities.getById(item.id)
entity ? viewer.zoomTo(entity) : createEntity(item)
}
const createEntity = (item) => {
const entity = { id: item.id }
if (item.type && item.property) {
entity[item.type] = item.property
}
if (item.position) {
entity['position'] = item.position
}
if (item.properties) {
entity['properties'] = item.properties
}
const e = viewer.entities.add(entity);
viewer.zoomTo(e)
}
// 鼠标点击事件
const clickEntity = () => {
handler.setInputAction(function (e) {
const pick = viewer.scene.pick(e.position);
if (cesium.defined(pick)) {
if (pick.id?.properties?.isDrag._value) {
dragEntity = pick.id
isDraging = true
viewer.scene.screenSpaceCameraController.enableRotate = false; // 锁定相机
} else {
ElMessage({ type: 'info', message: '不可拖拽' })
}
}
}, cesium.ScreenSpaceEventType.LEFT_DOWN);
}
// 鼠标移动
const mouseEntity = () => {
handler.setInputAction(function (movement) {
const scene = viewer.scene;
if (scene.mode !== cesium.SceneMode.MORPHING) {
if (scene.pickPositionSupported) {
const cartesian = viewer.scene.pickPosition(movement.endPosition);
if (cesium.defined(cartesian) && isDraging) {
if (dragEntity?.properties?.flag) {
dragEntity.position = cartesian
} else {
originalPositions = [...dragEntity.polygon.hierarchy.getValue().positions]; // 克隆顶点数组
const offset = cesium.Cartesian3.subtract(cartesian, originalPositions[0], new cesium.Cartesian3());
const newPositions = originalPositions.map(pos =>
cesium.Cartesian3.add(pos, offset, new cesium.Cartesian3())
);
dragEntity.polygon.hierarchy = new cesium.PolygonHierarchy(newPositions);
}
}
}
}
}, cesium.ScreenSpaceEventType.MOUSE_MOVE);
}
// 鼠标松开
const clickUp = () => {
handler.setInputAction(() => {
isDraging = false;
dragEntity = null;
originalPositions = []
viewer.scene.screenSpaceCameraController.enableRotate = true; // 解锁相机
}, cesium.ScreenSpaceEventType.LEFT_UP);
}
// 实体创建参数,这里做了一些改变,作为参数传递到createEntity函数种创建实体
const buttons = ref([
{
title: '矩形',
id: '1',
type: 'rectangle',
property: {
// Rectangle:由经度和纬度坐标指定的二维区域。
coordinates: cesium.Rectangle.fromDegrees(-92.0, 20.0, -86.0, 27.0),
outline: true, // 边缘线条
outlineColor: cesium.Color.WHITE,// 边缘线条颜色
outlineWidth: 4, // 边缘线条宽度
stRotation: cesium.Math.toRadians(45), // 一个数字属性,用于指定矩形纹理从北侧开始逆时针旋转的角度。
material: stripeMaterial, // 材质
},
},
{
title: '多边形',
id: '2',
type: 'polygon',
property: {
//PolygonHierarchy:定义多边形及其孔洞的线性环层次结构。这些孔洞自身也可能包含嵌套内部多边形的孔洞。
hierarchy: new cesium.PolygonHierarchy(
cesium.Cartesian3.fromDegreesArray([
-107.0, 27.0, -107.0, 22.0, -102.0, 23.0, -97.0, 21.0, -97.0, 25.0,
]),
),
outline: true,
outlineColor: cesium.Color.WHITE,
outlineWidth: 4,
material: stripeMaterial,
},
properties: {
isDrag: true,
}
},
{
title: '椭圆形',
id: '3',
type: 'ellipse',
position: cesium.Cartesian3.fromDegrees(-80.0, 25.0),
property: {
semiMinorAxis: 300000.0,// 半短轴
semiMajorAxis: 500000.0,// 半长轴
rotation: cesium.Math.toRadians(-40.0), // 向北旋转40°
stRotation: cesium.Math.toRadians(22),
material: stripeMaterial,
},
properties: {
isDrag: true,
flag: true
}
},
{
title: '圆形',
id: '4',
position: cesium.Cartesian3.fromDegrees(-72.0, 25.0),
type: "ellipse",
property: {
semiMinorAxis: 250000.0,
semiMajorAxis: 250000.0,
rotation: cesium.Math.toRadians(-40.0),
stRotation: cesium.Math.toRadians(90),
material: stripeMaterial,
},
properties: {
isDrag: true,
flag: true
}
}, {
title: '长方体',
id: '5',
type: 'rectangle',
property: {
coordinates: cesium.Rectangle.fromDegrees(-118.0, 38.0, -116.0, 40.0),
extrudedHeight: 500000.0, // 拉伸高度
outline: true,
outlineColor: cesium.Color.WHITE,
outlineWidth: 4,
stRotation: cesium.Math.toRadians(45),
// 使用提供的选项创建随机颜色。如需生成可重现的随机颜色,应在应用程序启动时调用一次
material: cesium.Color.fromRandom({ alpha: 1.0 }),
}
},
{
title: '椭圆柱',
id: '6',
type: 'ellipse',
position: cesium.Cartesian3.fromDegrees(-117.0, 35.0),
property: {
semiMinorAxis: 100000.0,
semiMajorAxis: 200000.0,
height: 100000.0,// 柱体高度
extrudedHeight: 10000.0,// 离地高度
rotation: cesium.Math.toRadians(90.0),//向南旋转90°
outline: true,
outlineColor: cesium.Color.WHITE,
outlineWidth: 4,
material: cesium.Color.fromRandom({ alpha: 1.0 })
},
properties: {
isDrag: true,
flag: true
}
},
{
title: '不规则多边体',
id: '7',
type: 'polygon',
property: {
hierarchy: new cesium.PolygonHierarchy(
cesium.Cartesian3.fromDegreesArray([
-118.0, 30.0, -115.0, 30.0, -117.1, 31.1, -118.0, 33.0,
]),
),
height: 300000.0,
extrudedHeight: 700000.0,
outline: true,
outlineColor: cesium.Color.WHITE,
outlineWidth: 4,
material: cesium.Color.fromRandom({ alpha: 1.0 }),
},
properties: {
isDrag: true,
}
}, {
title: '墙体',
id: '8',
type: 'wall',
position: null,
property: {
positions: cesium.Cartesian3.fromDegreesArray([
-95.0, 50.0, -85.0, 50.0, -75.0, 50.0,
]),
// 一个与位置平行的数组,用于指定各位置处墙体的最大高度。如果未定义,则使用每个位置的高度。
maximumHeights: [500000, 1000000, 500000],
// 一个与位置平行的数组,用于指定各位置处墙体的最小高度。如果未定义,则每个位置的高度为 0.0。
minimumHeights: [0, 500000, 0],
outline: true,
outlineColor: cesium.Color.LIGHTGRAY,
outlineWidth: 4,
material: cesium.Color.fromRandom({ alpha: 0.7 }),
}
},
{
title: '层级多变形',
id: '9',
type: 'polygon',
position: null,
property: {
//hierarchy:一个指定多边形层次结构的属性。
hierarchy: {
positions: cesium.Cartesian3.fromDegreesArray([
-109.0, 30.0, -95.0, 30.0, -95.0, 40.0, -109.0, 40.0,
]),
/**
描述了一个由线性环层次结构定义的多边形,这些线性环构成了外部形状和所有嵌套孔洞。
该多边形遵循地球曲率,可放置于地表或某一高度,还可选择拉伸为立体形状。
*/
holes: [
{
positions: cesium.Cartesian3.fromDegreesArray([
-107.0, 31.0, -107.0, 39.0, -97.0, 39.0, -97.0, 31.0,
]),
holes: [
{
positions: cesium.Cartesian3.fromDegreesArray([
-105.0, 33.0, -99.0, 33.0, -99.0, 37.0, -105.0, 37.0,
]),
holes: [
{
positions: cesium.Cartesian3.fromDegreesArray([
-103.0, 34.0, -101.0, 34.0, -101.0, 36.0, -103.0, 36.0,
]),
},
],
},
],
},
],
},
material: stripeMaterial,
},
properties: {
isDrag: false
}
},
{
title: '圆锥体',
id: '10',
type: 'cylinder',
position: cesium.Cartesian3.fromDegrees(-70.0, 40.0, 200000.0),
property: {
hierarchy: new cesium.PolygonHierarchy(
cesium.Cartesian3.fromDegreesArray([
-118.0, 30.0, -115.0, 30.0, -117.1, 31.1, -118.0, 33.0,
]),
),
length: 400000.0, // 一个指定圆柱体长度的数值属性。
topRadius: 0.0,
outline: true,
bottomRadius: 200000.0, // 一个指定圆柱体底部半径的数值属性。
material: cesium.Color.fromRandom({ alpha: 1.0 }),
numberOfVerticalLines: 11,// 垂直线条数:一个数值属性,用于指定沿轮廓周长绘制的垂直线条数量。
},
properties: {
isDrag: true,
flag: true
}
},
{
title: '墙合拢',
id: '11',
type: 'wall',
position: null,
property: {
positions: cesium.Cartesian3.fromDegreesArrayHeights([
-90.0, 43.0, 100000.0, -87.5, 45.0, 100000.0, -85.0, 43.0, 100000.0, -87.5,
41.0, 100000.0, -90.0, 43.0, 100000.0,
]),
material: new cesium.CheckerboardMaterialProperty({
repeat: new cesium.Cartesian2(20.0, 6.0),
}),
}
},
{
title: '廊道',
id: '12',
type: 'corridor',
position: null,
property: {
positions: cesium.Cartesian3.fromDegreesArray([
-120.0, 45.0, -125.0, 50.0, -125.0, 55.0,
]),
width: 100000,
height: 700000,
outline: true,
outlineColor: cesium.Color.WHITE,
outlineWidth: 4,
material: cesium.Color.fromRandom({ alpha: 0.7 }),
}
},
{
title: '折线体',
id: '13',
type: 'polylineVolume',
position: null,
property: {
positions: cesium.Cartesian3.fromDegreesArray([
-102.0, 15.0, -105.0, 20.0, -110.0, 20.0,
]),
shape: starPositions(7, 30000.0, 20000.0),
material: cesium.Color.fromRandom({ alpha: 1.0 }),
}
},
{
title: '折线体',
id: '14',
type: 'polylineVolume',
position: null,
property: {
positions: cesium.Cartesian3.fromDegreesArray([
-104.0, 13.0, -107.0, 18.0, -112.0, 18.0,
]),
// 一个指定笛卡尔二维坐标数组的属性,该数组定义了要拉伸的形状。
shape: computeCircle(40000.0),
material: cesium.Color.RED,
}
},
{
title: '广告牌',
id: '15',
type: 'billboard',
position: cesium.Cartesian3.fromDegrees(-75.59777, 40.03883),
property: {
show: true,// 控制显隐
image: logo,//图片地址
scale: 2.0,// 放大倍数
},
properties: {
isDrag: true,
flag: true
}
}
])
const home = () => {
viewer.camera.flyTo({
destination: cesium.Cartesian3.fromDegrees(-75.59777, 40.03883, 10000 * 2000),
orientation: {
heading: 0,
pitch: cesium.Math.toRadians(-90),
roll: 0
},
duration: 1.0
});
}
onMounted(async () => {
const earth = document.querySelector("#earth");
viewer = useCesium(earth);
handler = new cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
clickEntity()
mouseEntity()
clickUp()
});
</script>
<template>
<div class="entities">
<div class="earth" id="earth"></div>
<div class="settings">
<div class="flex fw-wrap ai-center">
<el-button type="primary" class="m5" plain size="small" @click="home">视角重置</el-button>
<el-button class="m5" v-for="item in buttons" :key="item.id" plain size="small" type="primary"
@click="clickBtn(item)">{{ item.title
}}</el-button>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.entities {
position: relative;
width: 100%;
height: 100%;
.earth {
width: 100%;
height: 100%;
z-index: 9999;
}
.settings {
position: absolute;
top: 0;
left: 0;
width: 200px;
height: 300px;
// background-color: aquamarine;
}
}
</style>
实现效果:

多边形部分拖拽:
这种功能主要时为了在地图上框选某个区域进行功能分析,实现思路,具体是为多边形每个顶点添加可拖拽标识,更新多边形顶点数组(需根据顶点ID找到对应索引),伪代码如下:
js
handler.setInputAction((movement) => {
if (draggedVertex) {
const newPos = viewer.scene.pickPosition(movement.endPosition);
if (newPos) {
draggedVertex.position = newPos; // 更新顶点位置
// 更新多边形顶点数组(需根据顶点ID找到对应索引)
const polygon = draggedVertex.parent; // 假设顶点实体关联到多边形
const index = polygon.vertexEntities.indexOf(draggedVertex);
polygon.positions[index] = newPos;
polygon.polygon.hierarchy = new cesium.PolygonHierarchy(polygon.positions);
}
}
}, cesium.ScreenSpaceEventType.MOUSE_MOVE);
总结:
文章一开始是想写一些实体加载,按照官方文档中的那样,想想还是放弃了,后来做了一些改动,但是改动不是很大,主要是将一些实体创建的参数拆到一起。有不足之处或者有好的想法欢迎大家下方留言!