一、上/下/左/右整体移动
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Cesium 点线面整体平移</title>
<!-- 引入Cesium CDN -->
<script src="https://cesium.com/downloads/cesiumjs/releases/1.108/Build/Cesium/Cesium.js"></script>
<link href="https://cesium.com/downloads/cesiumjs/releases/1.108/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
#cesiumContainer {
width: 100vw;
height: 100vh;
position: relative;
}
/* 平移按钮样式 */
.move-controls {
position: absolute;
top: 20px;
left: 20px;
z-index: 100;
display: grid;
grid-template-columns: repeat(3, 40px);
grid-template-rows: repeat(3, 40px);
gap: 5px;
}
.move-btn {
border: none;
background: rgba(0, 123, 255, 0.8);
color: white;
font-size: 16px;
border-radius: 4px;
cursor: pointer;
transition: background 0.3s;
}
.move-btn:hover {
background: rgba(0, 86, 179, 0.8);
}
/* 按钮布局:上、下、左、右居中 */
.btn-up {
grid-area: 1 / 2 / 2 / 3;
}
.btn-down {
grid-area: 3 / 2 / 4 / 3;
}
.btn-left {
grid-area: 2 / 1 / 3 / 2;
}
.btn-right {
grid-area: 2 / 3 / 3 / 4;
}
</style>
</head>
<body>
<div id="cesiumContainer">
<!-- 平移控制按钮 -->
<div class="move-controls">
<button class="move-btn btn-up">↑</button>
<button class="move-btn btn-down">↓</button>
<button class="move-btn btn-left">←</button>
<button class="move-btn btn-right">→</button>
</div>
</div>
<script>
// 1. 初始化Cesium场景
Cesium.Ion.defaultAccessToken = 'Access Token'
const viewer = new Cesium.Viewer("cesiumContainer", {
// terrainProvider: Cesium.createWorldTerrain(),
timeline: false,
animation: false
});
viewer.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(116.403963, 39.915112, 1000) // 聚焦北京天安门附近
});
// 2. 定义平移增量(每次点击移动的距离,单位:度(经纬度)、米(高度))
// 注:1度纬度≈111公里,此处设置0.001度≈111米,平移效果更直观
const MOVE_OFFSET = {
lon: 0.001, // 经度增量(左右移动)
lat: 0.001, // 纬度增量(上下移动)
height: 0 // 高度增量(可按需调整,实现垂直升降)
};
// 3. 创建测试实体(点、线、面)
let testEntities = [];
// 3.1 创建点实体
const pointEntity = viewer.entities.add({
name: "测试点",
position: Cesium.Cartesian3.fromDegrees(116.403963, 39.915112, 100),
point: {
pixelSize: 10,
color: Cesium.Color.RED,
outlineColor: Cesium.Color.WHITE,
outlineWidth: 2
}
});
// 3.2 创建线实体
const polylineEntity = viewer.entities.add({
name: "测试线",
polyline: {
positions: Cesium.Cartesian3.fromDegreesArray([
116.402963, 39.914112,
116.404963, 39.915112,
116.403963, 39.916112
]),
width: 5,
material: Cesium.Color.BLUE,
clampToGround: false
}
});
// 3.3 创建面实体
const polygonEntity = viewer.entities.add({
name: "测试面",
polygon: {
hierarchy: Cesium.Cartesian3.fromDegreesArray([
116.400963, 39.913112,
116.405963, 39.913112,
116.405963, 39.917112,
116.400963, 39.917112
]),
material: Cesium.Color.GREEN.withAlpha(0.5),
outline: true,
outlineColor: Cesium.Color.DARK_GREEN,
outlineWidth: 2,
clampToGround: false
}
});
// 收集所有测试实体
testEntities = [pointEntity, polylineEntity, polygonEntity];
// 4. 核心:平移实体的通用方法
/**
* 平移Cesium实体(支持点、线、面)
* @param {Cesium.Entity} entity - 要平移的实体
* @param {number} lonOffset - 经度偏移量(正值右移,负值左移)
* @param {number} latOffset - 纬度偏移量(正值上移,负值下移)
* @param {number} heightOffset - 高度偏移量(正值上升,负值下降)
*/
function translateEntity(entity, lonOffset, latOffset, heightOffset) {
// 情况1:点实体(拥有position属性)
if (entity.position) {
// 获取当前笛卡尔坐标
const currentCartesian = entity.position.getValue(Cesium.JulianDate.now());
if (!currentCartesian) return;
// 转换为地理坐标(经纬度+高度)
const currentCartographic = Cesium.Cartographic.fromCartesian(currentCartesian);
// 计算新的地理坐标
const newLon = currentCartographic.longitude + Cesium.Math.toRadians(lonOffset);
const newLat = currentCartographic.latitude + Cesium.Math.toRadians(latOffset);
const newHeight = currentCartographic.height + heightOffset;
// 转换回笛卡尔坐标并更新实体位置
const newCartesian = Cesium.Cartesian3.fromRadians(newLon, newLat, newHeight);
entity.position = newCartesian;
}
// 情况2:线/面实体(拥有polyline.positions 或 polygon.hierarchy)
// 提取所有需要平移的坐标点
let positions = null;
if (entity.polyline && entity.polyline.positions) {
positions = entity.polyline.positions.getValue(Cesium.JulianDate.now());
} else if (entity.polygon && entity.polygon.hierarchy) {
const hierarchy = entity.polygon.hierarchy.getValue(Cesium.JulianDate.now());
positions = hierarchy ? hierarchy.positions : null;
}
if (positions && positions.length > 0) {
const newPositions = [];
for (let i = 0; i < positions.length; i++) {
// 单个坐标点的平移逻辑(与点实体一致)
const currentCartographic = Cesium.Cartographic.fromCartesian(positions[i]);
const newLon = currentCartographic.longitude + Cesium.Math.toRadians(lonOffset);
const newLat = currentCartographic.latitude + Cesium.Math.toRadians(latOffset);
const newHeight = currentCartographic.height + heightOffset;
const newCartesian = Cesium.Cartesian3.fromRadians(newLon, newLat, newHeight);
newPositions.push(newCartesian);
}
// 更新线/面的坐标集合
if (entity.polyline) {
entity.polyline.positions = newPositions;
} else if (entity.polygon) {
entity.polygon.hierarchy = new Cesium.PolygonHierarchy(newPositions);
}
}
}
// 5. 绑定按钮点击事件,实现上下左右平移
document.querySelector(".btn-up").addEventListener("click", () => {
// 上移:纬度增加(北纬越大,位置越北)
testEntities.forEach(entity => {
translateEntity(entity, 0, MOVE_OFFSET.lat, MOVE_OFFSET.height);
});
});
document.querySelector(".btn-down").addEventListener("click", () => {
// 下移:纬度减少
testEntities.forEach(entity => {
translateEntity(entity, 0, -MOVE_OFFSET.lat, MOVE_OFFSET.height);
});
});
document.querySelector(".btn-left").addEventListener("click", () => {
// 左移:经度减少(东经越大,位置越东,左移即经度减小)
testEntities.forEach(entity => {
translateEntity(entity, -MOVE_OFFSET.lon, 0, MOVE_OFFSET.height);
});
});
document.querySelector(".btn-right").addEventListener("click", () => {
// 右移:经度增加
testEntities.forEach(entity => {
translateEntity(entity, MOVE_OFFSET.lon, 0, MOVE_OFFSET.height);
});
});
</script>
</body>
</html>
核心方法解析(translateEntity):
该方法是实现平移的核心,兼容点、线、面三种实体类型,核心流程为:获取当前Cartesian3坐标 → 转换为Cartographic地理坐标 → 叠加平移增量 → 转换回Cartesian3坐标 → 更新实体位置。
线 / 面实体的处理逻辑是遍历其所有顶点坐标,对每个顶点执行相同的平移操作,从而实现整体移动,保证形状不发生变化。
平移增量调整:
MOVE_OFFSET中定义了经纬度和高度的增量,可根据需求调整:
- 增大lon/lat可提高平移速度,减小则更精细。
- 调整height可实现实体的垂直升降(如设置为 10,每次点击上升 10 米)。
二、旋转
保持形状、大小不变
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="https://cesium.com/downloads/cesiumjs/releases/1.110/Build/Cesium/Cesium.js"></script>
<link href="https://cesium.com/downloads/cesiumjs/releases/1.110/Build/Cesium/Widgets/widgets.css" rel="stylesheet">
</head>
<body>
<div id="cesiumContainer"></div>
<div style="position: absolute; top: 10px; left: 10px; z-index: 1000;">
<button id="clockwiseBtn">顺时针旋转</button>
<button id="counterclockwiseBtn">逆时针旋转</button>
<input type="number" id="rotationAngle" value="15" min="1" max="90">
<label for="rotationAngle">旋转角度(度)</label>
</div>
<script>
Cesium.Ion.defaultAccessToken = 'Access Token'
// 初始化Cesium Viewer
const viewer = new Cesium.Viewer('cesiumContainer', {
// terrainProvider: Cesium.createWorldTerrain(),
baseLayerPicker: false,
animation: false,
timeline: false
});
// 存储原始图形数据
let originalEntities = [];
let isOriginalDataSaved = false;
// 创建示例图形(点、线、面)
function createExampleEntities() {
// 清除现有实体
viewer.entities.removeAll();
originalEntities = [];
// 示例多边形顶点(三角形)
const polygonPositions = Cesium.Cartesian3.fromDegreesArray([
-115.0, 37.0,
-115.0, 32.0,
-107.0, 33.0
]);
// 创建面(多边形)
const polygonEntity = viewer.entities.add({
polygon: {
hierarchy: polygonPositions,
material: Cesium.Color.GREEN.withAlpha(0.5),
outline: true,
outlineColor: Cesium.Color.GREEN
}
});
// 创建线(多段线)
const polylineEntity = viewer.entities.add({
polyline: {
positions: polygonPositions,
width: 3,
material: Cesium.Color.BLUE
}
});
// 创建点(在顶点位置)
const pointEntities = polygonPositions.map((position, index) => {
return viewer.entities.add({
position: position,
point: {
pixelSize: 10,
color: Cesium.Color.RED
},
label: {
text: `点 ${index + 1}`,
font: '14px sans-serif',
pixelOffset: new Cesium.Cartesian2(0, 20)
}
});
});
// 保存原始数据
originalEntities.push({
type: 'polygon',
positions: polygonPositions
});
originalEntities.push({
type: 'polyline',
positions: polygonPositions
});
pointEntities.forEach((entity, index) => {
originalEntities.push({
type: 'point',
position: polygonPositions[index],
entity: entity
});
});
// 缩放到图形范围
viewer.zoomTo(viewer.entities);
isOriginalDataSaved = true;
}
// 计算图形的几何中心
function calculateGeometricCenter(positions) {
const cartographics = positions.map(cartesian =>
Cesium.Cartographic.fromCartesian(cartesian)
);
// 计算平均经纬度和高度
const avgLat = cartographics.reduce((sum, carto) => sum + carto.latitude, 0) / cartographics.length;
const avgLon = cartographics.reduce((sum, carto) => sum + carto.longitude, 0) / cartographics.length;
const avgHeight = cartographics.reduce((sum, carto) => sum + carto.height, 0) / cartographics.length;
return Cesium.Cartesian3.fromRadians(avgLon, avgLat, avgHeight);
}
// 旋转函数
function rotateEntities(angleDegrees, clockwise = true) {
if (!isOriginalDataSaved) {
console.log("请先创建图形");
return;
}
const angle = clockwise ? -angleDegrees : angleDegrees;
const angleRadians = Cesium.Math.toRadians(angle);
// 计算所有原始顶点的中心点
const allPositions = originalEntities
.filter(entity => entity.positions || entity.position)
.flatMap(entity => entity.positions ? entity.positions : [entity.position]);
const center = calculateGeometricCenter(allPositions);
// 获取当前所有实体
const entities = viewer.entities.values;
entities.forEach(entity => {
if (entity.polygon) {
// 旋转多边形
const polygonPositions = entity.polygon.hierarchy.getValue(Cesium.JulianDate.now());
const rotatedPositions = rotatePositions(polygonPositions.positions, center, angleRadians);
entity.polygon.hierarchy = rotatedPositions;
}
if (entity.polyline) {
// 旋转线
const polylinePositions = entity.polyline.positions.getValue(Cesium.JulianDate.now());
const rotatedPositions = rotatePositions(polylinePositions, center, angleRadians);
entity.polyline.positions = rotatedPositions;
}
if (entity.point) {
// 旋转点
const position = entity.position.getValue(Cesium.JulianDate.now());
const rotatedPosition = rotatePosition(position, center, angleRadians);
entity.position = rotatedPosition;
}
});
}
// 旋转单个点
function rotatePosition(position, center, angleRadians) {
// 将笛卡尔坐标转换为经纬度
const cartographic = Cesium.Cartographic.fromCartesian(position);
const centerCartographic = Cesium.Cartographic.fromCartesian(center);
// 计算相对中心点的经纬度差
const deltaLon = cartographic.longitude - centerCartographic.longitude;
const deltaLat = cartographic.latitude - centerCartographic.latitude;
// 使用平面近似(在小范围内有效)
const x = deltaLon * Math.cos(centerCartographic.latitude);
const y = deltaLat;
// 应用2D旋转矩阵
const cosA = Math.cos(angleRadians);
const sinA = Math.sin(angleRadians);
const xRotated = x * cosA - y * sinA;
const yRotated = x * sinA + y * cosA;
// 转换回经纬度
const rotatedLon = centerCartographic.longitude + xRotated / Math.cos(centerCartographic.latitude);
const rotatedLat = centerCartographic.latitude + yRotated;
return Cesium.Cartesian3.fromRadians(rotatedLon, rotatedLat, cartographic.height);
}
// 旋转位置数组
function rotatePositions(positions, center, angleRadians) {
const rotatedPositions = [];
for (let i = 0; i < positions.length; i++) {
rotatedPositions.push(rotatePosition(positions[i], center, angleRadians));
}
return rotatedPositions;
}
// 重置到原始状态
function resetToOriginal() {
if (!isOriginalDataSaved) return;
viewer.entities.removeAll();
// 使用原始数据重新创建实体
originalEntities.forEach(original => {
if (original.type === 'polygon') {
viewer.entities.add({
polygon: {
hierarchy: original.positions,
material: Cesium.Color.GREEN.withAlpha(0.5),
outline: true,
outlineColor: Cesium.Color.GREEN
}
});
} else if (original.type === 'polyline') {
viewer.entities.add({
polyline: {
positions: original.positions,
width: 3,
material: Cesium.Color.BLUE
}
});
}
// 点会在后续步骤中重新创建
});
}
// 事件监听
document.getElementById('clockwiseBtn').addEventListener('click', () => {
const angle = parseFloat(document.getElementById('rotationAngle').value);
rotateEntities(angle, true);
});
document.getElementById('counterclockwiseBtn').addEventListener('click', () => {
const angle = parseFloat(document.getElementById('rotationAngle').value);
rotateEntities(angle, false);
});
// 添加重置按钮(可选)
const resetBtn = document.createElement('button');
resetBtn.textContent = '重置图形';
resetBtn.style.marginLeft = '10px';
resetBtn.addEventListener('click', resetToOriginal);
document.querySelector('div[style]').appendChild(resetBtn);
// 初始化创建示例图形
createExampleEntities();
</script>
</body>
</html>
关键特性
统一旋转中心:计算所有几何图形的几何中心作为旋转中心
保持形状大小:通过保持各点到中心的距离不变来实现
支持多种图形:同时处理点、线、面
可调旋转角度:可以通过输入框设置旋转角度
双向旋转:支持顺时针和逆时针旋转
注意事项
这个实现在小范围内(几十公里)效果较好
对于大范围的图形,需要考虑地球曲率的影响
旋转操作会修改原始图形数据,可以通过重置功能恢复