不得不说,AI是真的强大,这篇代码全是AI写的,当然也不是一次写成功,cursor询问+修改大概20次改好,请看效果图
Video_20250616154259
这里的Cesium版本是1.116.0,下面的代码是组件ImageLoad,将这个组件加载到页面上,就可以执行贴图和旋转功能,imageUrl自己引入,图片四个角的经纬度这里是写成固定的数值
<template>
<div :class="[prefixCls, 'shapefile-container mr-8px']">
<BaseButton type="primary" @click="handleImageLoad">贴图</BaseButton>
<BaseButton type="success" @click="testWithNetworkImage" style="margin-top: 10px">测试网络图片</BaseButton>
<BaseButton type="warning" @click="testLocalImage" style="margin-top: 10px">测试本地生成图片</BaseButton>
<BaseButton type="info" @click="showAllEntities" style="margin-top: 10px">显示所有实体</BaseButton>
<BaseButton type="warning" @click="goToChina" style="margin-top: 10px">定位到中国</BaseButton>
<!-- 修改旋转控制面板,移除状态显示 -->
<div v-if="showRotationControl" class="rotation-panel">
<div class="control-section">
<h3>图片旋转控制</h3>
<div class="rotation-controls">
<el-button @click="rotate(-5)">-5°</el-button>
<el-slider
v-model="rotationAngle"
:min="0"
:max="360"
@input="handleRotationChange"
/>
<el-button @click="rotate(5)">+5°</el-button>
</div>
<div class="angle-display">当前角度:{{ rotationAngle }}°</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { onBeforeUnmount, ref, onMounted, computed, ComputedRef } from 'vue';
import { ElIcon, ElMessage, ElButton, ElSlider } from 'element-plus';
import { Location, Crop, RefreshRight, Aim, DataLine, ArrowRightBold, ArrowRight } from '@element-plus/icons-vue';
import { useDesign } from '@/hooks/web/useDesign';
import { useCesiumStore } from '@/store/modules/cesium';
import TargetDetection from './TargetDetection.vue';
import { DataTree } from '@/views/RemoteSensing/components/DataRetrievalTree.vue';
const cesiumStore = useCesiumStore();
const { getPrefixCls } = useDesign();
const prefixCls = getPrefixCls('shapefile-uploader');
const isCollapsed = ref(false);
const showTargetDetection = ref(false);
const showRotationControl = ref(false);
const rotationAngle = ref(0);
const currentImageEntity = ref<any>(null);
const originalModelMatrix = ref<any>(null); // 保存原始的ModelMatrix
const centerPosition = ref<any>(null); // 保存旋转中心点
// 添加缺少的变量定义
let currentPrimitive: any = null; // 当前的Primitive对象
const linkInfo: ComputedRef<DataTree> = computed(() => cesiumStore.getCurLinkInfo);
// 新增:移除当前的Primitive
const removePrimitive = () => {
if (currentPrimitive && window.GlobalViewer) {
console.log('移除Primitive');
try {
window.GlobalViewer.scene.primitives.remove(currentPrimitive);
} catch (error) {
console.warn('移除Primitive时出错:', error);
}
currentPrimitive = null;
}
};
// 修改创建图片实体的函数,支持旋转
const createNonStretchedImageEntity = (imageUrl: string, coordinates: any) => {
return new Promise((resolve, reject) => {
// 计算中心点
const centerLon = (coordinates.leftTop.lon + coordinates.rightBottom.lon) / 2;
const centerLat = (coordinates.leftTop.lat + coordinates.rightBottom.lat) / 2;
centerPosition.value = Cesium.Cartesian3.fromDegrees(centerLon, centerLat, 0);
const positions = [
Cesium.Cartesian3.fromDegrees(coordinates.leftBottom.lon, coordinates.leftBottom.lat),
Cesium.Cartesian3.fromDegrees(coordinates.rightBottom.lon, coordinates.rightBottom.lat),
Cesium.Cartesian3.fromDegrees(coordinates.rightTop.lon, coordinates.rightTop.lat),
Cesium.Cartesian3.fromDegrees(coordinates.leftTop.lon, coordinates.leftTop.lat),
];
Cesium.Resource.fetchImage(imageUrl)
.then((image) => {
// 简化纹理坐标(不做宽高比调整)
const textureCoordinates = new Float32Array([
0, 0, // 左下
1, 0, // 右下
1, 1, // 右上
0, 1, // 左上
]);
const geometry = new Cesium.Geometry({
attributes: {
position: new Cesium.GeometryAttribute({
componentDatatype: Cesium.ComponentDatatype.DOUBLE,
componentsPerAttribute: 3,
values: Cesium.Cartesian3.packArray(positions),
}),
st: new Cesium.GeometryAttribute({
componentDatatype: Cesium.ComponentDatatype.FLOAT,
componentsPerAttribute: 2,
values: textureCoordinates,
}),
},
indices: new Uint16Array([0, 1, 2, 0, 2, 3]),
primitiveType: Cesium.PrimitiveType.TRIANGLES,
boundingSphere: Cesium.BoundingSphere.fromPoints(positions),
});
const material = new Cesium.Material({
fabric: {
type: 'Image',
uniforms: {
image: imageUrl,
},
},
});
const instance = new Cesium.GeometryInstance({
geometry: geometry,
id: 'customImageOverlay',
});
// 保存原始ModelMatrix
originalModelMatrix.value = Cesium.Matrix4.IDENTITY.clone();
const primitive = new Cesium.Primitive({
geometryInstances: instance,
appearance: new Cesium.MaterialAppearance({
material: material,
translucent: true,
flat: true,
}),
asynchronous: false,
modelMatrix: originalModelMatrix.value
});
window.GlobalViewer.scene.primitives.add(primitive);
resolve(primitive);
})
.catch(reject);
});
};
// 备选方案:使用四元数来实现2D平面旋转
const applyRotationToMatrix = (angle: number) => {
if (!currentPrimitive || !centerPosition.value) return;
const angleRadians = Cesium.Math.toRadians(angle);
// 获取地面的向上方向(法向量)
const ellipsoid = Cesium.Ellipsoid.WGS84;
const normal = ellipsoid.geodeticSurfaceNormal(centerPosition.value);
// 使用四元数创建围绕法向量的旋转
const quaternion = Cesium.Quaternion.fromAxisAngle(normal, angleRadians);
const rotationMatrix = Cesium.Matrix3.fromQuaternion(quaternion);
// 创建4x4变换矩阵
const transform = Cesium.Matrix4.fromRotationTranslation(rotationMatrix, centerPosition.value);
// 创建平移到原点的矩阵
const centerToOrigin = Cesium.Matrix4.fromTranslation(
Cesium.Cartesian3.negate(centerPosition.value, new Cesium.Cartesian3())
);
// 组合变换
const combinedMatrix = Cesium.Matrix4.multiply(
transform,
centerToOrigin,
new Cesium.Matrix4()
);
// 应用变换
currentPrimitive.modelMatrix = combinedMatrix;
};
// 添加变量来保存当前图片URL和原始坐标
const currentImageUrl = ref<string>('');
const originalCoordinates = ref<any>(null);
// 修改handleImageLoad函数,保存必要信息
const handleImageLoad = async () => {
console.log('开始贴图...');
if (!window.GlobalViewer) {
console.error('GlobalViewer 不存在');
ElMessage.error('地图未初始化');
return;
}
// 原始不规则四边形坐标
const coordinates = {
leftTop: { lon: 116.412, lat: 31.696 },
rightTop: { lon: 116.418, lat: 31.705 },
rightBottom: { lon: 116.508, lat: 31.6605 },
leftBottom: { lon: 116.5022, lat: 31.6516 },
};
const imageUrl = '/testJpgPng/2018_1119_3m.jpg';
// 保存当前信息
currentImageUrl.value = imageUrl;
originalCoordinates.value = coordinates;
try {
// 先清理旧的实体和Primitive
if (currentImageEntity.value) {
console.log('移除旧实体');
window.GlobalViewer.entities.remove(currentImageEntity.value);
currentImageEntity.value = null;
}
if (currentPrimitive) {
console.log('移除旧Primitive');
window.GlobalViewer.scene.primitives.remove(currentPrimitive);
currentPrimitive = null;
}
// 重置旋转角度
rotationAngle.value = 0;
// 创建新的贴图
const primitive = await createNonStretchedImageEntity(imageUrl, coordinates);
currentPrimitive = primitive;
// 只在第一次创建时定位
if (!showRotationControl.value) {
const centerLon = (coordinates.leftTop.lon + coordinates.rightBottom.lon) / 2;
const centerLat = (coordinates.leftTop.lat + coordinates.rightBottom.lat) / 2;
setTimeout(() => {
window.GlobalViewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(centerLon, centerLat, 5000),
orientation: {
heading: Cesium.Math.toRadians(0),
pitch: Cesium.Math.toRadians(-45),
roll: 0.0,
},
duration: 2.0,
});
}, 500);
}
showRotationControl.value = true;
ElMessage.success('图片贴图成功');
} catch (error) {
console.error('贴图失败:', error);
ElMessage.error('贴图失败: ' + error.message);
}
};
// 移除防抖处理,直接实时更新
const handleRotationChange = (value: number) => {
rotationAngle.value = value;
applyRotationToMatrix(value); // 直接更新,无需防抖
};
// 简化按钮旋转函数
const rotate = (delta: number) => {
rotationAngle.value = (rotationAngle.value + delta + 360) % 360;
applyRotationToMatrix(rotationAngle.value);
};
// 修改testLocalImage函数,也保存必要信息
const testLocalImage = () => {
console.log('测试本地图片...');
if (!window.GlobalViewer) {
ElMessage.error('地图未初始化');
return;
}
const coordinates = {
leftTop: { lon: 116.412, lat: 31.696 },
rightTop: { lon: 116.418, lat: 31.705 },
rightBottom: { lon: 116.508, lat: 31.6605 },
leftBottom: { lon: 116.5022, lat: 31.6516 },
};
// 测试一个简单的颜色图片
const canvas = document.createElement('canvas');
canvas.width = 256;
canvas.height = 256;
const ctx = canvas.getContext('2d');
// 创建一个渐变测试图
const gradient = ctx.createLinearGradient(0, 0, 256, 256);
gradient.addColorStop(0, '#ff0000');
gradient.addColorStop(0.5, '#00ff00');
gradient.addColorStop(1, '#0000ff');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 256, 256);
// 添加一些文字
ctx.fillStyle = 'white';
ctx.font = '20px Arial';
ctx.fillText('Test Image', 80, 130);
const testImageUrl = canvas.toDataURL('image/png');
// 保存当前信息
currentImageUrl.value = testImageUrl;
originalCoordinates.value = coordinates;
try {
removePrimitive();
if (currentImageEntity.value) {
window.GlobalViewer.entities.remove(currentImageEntity.value);
currentImageEntity.value = null;
}
createNonStretchedImageEntity(testImageUrl, coordinates)
.then((primitive) => {
currentPrimitive = primitive;
showRotationControl.value = true;
window.GlobalViewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(116.45, 31.67, 5000),
duration: 2.0,
});
ElMessage.success('测试图片贴图成功');
})
.catch((error) => {
console.error('测试贴图失败:', error);
ElMessage.error('测试贴图失败');
});
} catch (error) {
console.error('测试贴图失败:', error);
ElMessage.error('测试贴图失败');
}
};
// 添加一个测试方法,用网络图片测试
const testWithNetworkImage = () => {
console.log('使用网络图片测试...');
if (!window.GlobalViewer) {
ElMessage.error('地图未初始化');
return;
}
// 使用一个可靠的网络图片进行测试
const testImageUrl = 'https://cesium.com/img/cesiumjs/cesium-icon.png';
const coordinates = {
leftTop: { lon: 116.412, lat: 31.696 },
rightTop: { lon: 116.418, lat: 31.705 },
rightBottom: { lon: 116.508, lat: 31.6605 },
leftBottom: { lon: 116.5022, lat: 31.6516 },
};
try {
if (currentImageEntity.value) {
console.log('remove', window.GlobalViewer.entities);
window.GlobalViewer.entities.remove(currentImageEntity.value);
currentImageEntity.value = null;
}
const hierarchy = new Cesium.PolygonHierarchy([
Cesium.Cartesian3.fromDegrees(coordinates.leftBottom.lon, coordinates.leftBottom.lat),
Cesium.Cartesian3.fromDegrees(coordinates.leftTop.lon, coordinates.leftTop.lat),
Cesium.Cartesian3.fromDegrees(coordinates.rightTop.lon, coordinates.rightTop.lat),
Cesium.Cartesian3.fromDegrees(coordinates.rightBottom.lon, coordinates.rightBottom.lat),
]);
currentImageEntity.value = window.GlobalViewer.entities.add({
name: '测试贴图',
polygon: {
hierarchy: hierarchy,
material: new Cesium.ImageMaterialProperty({
image: testImageUrl,
}),
height: 0,
heightReference: Cesium.HeightReference.CLAMP_TO_GROUND,
},
});
window.GlobalViewer.zoomTo(currentImageEntity.value);
ElMessage.success('测试图片贴图成功');
} catch (error) {
console.error('测试贴图失败:', error);
ElMessage.error('测试贴图失败');
}
};
// 添加调试方法
const showAllEntities = () => {
if (window.GlobalViewer) {
console.log('当前所有实体:', window.GlobalViewer.entities.values);
console.log('实体数量:', window.GlobalViewer.entities.values.length);
if (window.GlobalViewer.entities.values.length > 0) {
window.GlobalViewer.zoomTo(window.GlobalViewer.entities);
}
}
};
const goToChina = () => {
if (window.GlobalViewer) {
// 定位到中国区域
window.GlobalViewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(116.4074, 39.9042, 1000000), // 北京上空1000公里
duration: 3.0,
});
}
};
// 修改清理函数,移除定时器相关代码
onBeforeUnmount(() => {
console.log('ImageLoad 组件卸载');
// 清理实体
if (currentImageEntity.value && window.GlobalViewer) {
try {
window.GlobalViewer.entities.remove(currentImageEntity.value);
currentImageEntity.value = null;
} catch (error) {
console.error('清理实体失败:', error);
}
}
// 清理Primitive
removePrimitive();
// 清理引用
originalModelMatrix.value = null;
centerPosition.value = null;
});
// 修改相机移动事件监听
onMounted(() => {
console.log('ImageLoad 组件已挂载');
});
</script>
<style lang="scss" scoped>
$prefix-cls: '#{$namespace}-shapefile-uploader';
// 添加一个外层容器类
.shapefile-container {
position: absolute;
top: 120px;
right: 20px;
&.collapsed {
width: 0 !important;
}
}
.collapse-trigger {
position: absolute;
left: -20px;
top: 20px;
width: 14px;
height: 30px;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
transition: all 0.3s;
.trigger-text {
writing-mode: vertical-lr;
padding: 8px 6px;
background-color: var(--el-color-primary);
color: #fff;
// border-radius: 4px 0 0 4px;
font-size: 14px;
display: flex;
flex-direction: row;
align-items: center;
.text-char {
margin-top: 2px;
}
}
.trigger-icon {
width: 14px;
height: 30px;
background-color: var(--el-color-primary);
color: #fff;
border-radius: 2px;
display: flex;
align-items: center;
justify-content: center;
:deep(.el-icon) {
font-size: 12px;
color: #fff;
}
}
}
.content-wrapper {
background-color: var(--el-bg-color);
border-radius: 8px;
box-shadow: var(--el-box-shadow-light);
height: 100%;
}
// 添加过渡动画
.fade-slide-enter-active,
.fade-slide-leave-active {
transition: all 0.3s ease-in-out;
}
.fade-slide-enter-from,
.fade-slide-leave-to {
opacity: 0;
transform: translateX(30px);
}
.#{$prefix-cls} {
background-color: var(--el-bg-color);
border-radius: 8px;
box-shadow: var(--el-box-shadow-light);
&__header {
padding: 16px 20px;
border-bottom: 1px solid var(--el-border-color-light);
}
&__title {
margin: 0;
font-size: 16px;
font-weight: 500;
color: var(--el-text-color-primary);
}
&__content {
padding: 12px;
.tool-list {
list-style: none;
padding: 0;
margin: 0;
width: 100%;
}
.tool-item {
width: calc(100% - 24px - 2px);
height: 88px;
position: relative;
display: flex;
padding: 12px;
margin-bottom: 12px;
border-radius: 8px;
border: 1px solid var(--el-fill-color-dark);
cursor: pointer;
transition: all 0.5s;
align-items: center;
.tool-icon {
display: flex;
align-items: center;
justify-content: center;
width: 40px;
height: 40px;
margin-right: 12px;
border-radius: 8px;
background: var(--el-color-primary-light-9);
:deep(.el-icon) {
font-size: 24px;
color: var(--el-color-primary);
}
}
.tool-info {
flex: 1;
}
.tool-title {
display: flex;
align-items: center;
font-size: 14px;
font-weight: 500;
color: var(--el-text-color-primary);
margin-bottom: 4px;
.tool-badge {
margin-left: 8px;
padding: 2px 6px;
font-size: 12px;
border-radius: 4px;
background: var(--el-color-primary-light-8);
color: var(--el-color-primary);
}
}
.tool-desc {
font-size: 12px;
color: var(--el-text-color-secondary);
line-height: 1.4;
}
.tool-arrow {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
width: 14px;
height: 24px;
// border-radius: 50%;
background-color: var(--el-color-primary);
display: flex;
align-items: center;
justify-content: center;
opacity: 0;
transition: all 0.5s;
border-radius: 4px;
:deep(.el-icon) {
font-size: 10px;
color: #fff;
}
}
&:hover {
border-color: var(--el-color-primary);
.tool-arrow {
opacity: 1;
right: 8px;
}
}
}
}
}
.rotation-panel {
position: absolute;
top: 50px;
right: 0;
background: var(--el-bg-color);
padding: 15px;
border-radius: 8px;
box-shadow: var(--el-box-shadow-light);
width: 300px;
z-index: 1000;
.control-section {
h3 {
margin: 0 0 10px 0;
font-size: 14px;
color: var(--el-text-color-primary);
}
.rotation-controls {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 10px;
:deep(.el-slider) {
flex: 1;
}
}
.angle-display {
text-align: center;
color: var(--el-text-color-secondary);
font-size: 12px;
}
}
}
</style>