You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

1574 lines
44 KiB

<template>
<div class="cesium-container">
<div id="cesiumViewer" ref="cesiumViewer"></div>
<!-- 主工具栏 -->
<div class="main-toolbar" v-if="drawDomClick" :class="{ collapsed: isToolbarCollapsed }">
<!-- 展开/收起按钮 -->
<!-- <div class="collapse-btn" @click="toggleToolbar">
<span class="collapse-icon">{{ isToolbarCollapsed ? '▶' : '◀' }}</span>
<span class="collapse-text" v-if="!isToolbarCollapsed">收起</span>
</div> -->
<!-- 工具栏内容 -->
<div class="toolbar-content" v-show="!isToolbarCollapsed">
<div class="toolbar-group">
<button
@click="toggleDrawing('point')"
:class="{ active: drawingMode === 'point' }"
title="点"
class="tool-btn"
>
<span class="icon">📍</span>
<span class="text">{{ drawingMode === 'point' ? '停止' : '点' }}</span>
</button>
<button
@click="toggleDrawing('line')"
:class="{ active: drawingMode === 'line' }"
title="线"
class="tool-btn"
>
<span class="icon">📏</span>
<span class="text">{{ drawingMode === 'line' ? '停止' : '线' }}</span>
</button>
<button
@click="toggleDrawing('polygon')"
:class="{ active: drawingMode === 'polygon' }"
title="面"
class="tool-btn"
>
<span class="icon">🔶</span>
<span class="text">{{ drawingMode === 'polygon' ? '停止' : '面' }}</span>
</button>
</div>
<div class="toolbar-group">
<button
@click="toggleDrawing('rectangle')"
:class="{ active: drawingMode === 'rectangle' }"
title="矩形"
class="tool-btn"
>
<span class="icon">⬜</span>
<span class="text">{{ drawingMode === 'rectangle' ? '停止' : '矩形' }}</span>
</button>
<button
@click="toggleDrawing('circle')"
:class="{ active: drawingMode === 'circle' }"
title="圆形"
class="tool-btn"
>
<span class="icon">⭕</span>
<span class="text">{{ drawingMode === 'circle' ? '停止' : '圆形' }}</span>
</button>
</div>
<div class="toolbar-group">
<button
@click="clearAll"
:disabled="!allEntities.length"
title="清除所有"
class="tool-btn danger"
>
<span class="icon">🗑️</span>
<span class="text">清除</span>
</button>
<button
@click="exportData"
:disabled="!allEntities.length"
title="导出数据"
class="tool-btn success"
>
<span class="icon">💾</span>
<span class="text">导出</span>
</button>
</div>
</div>
<!-- 收起时的最小化工具栏 -->
<div class="toolbar-minimized" v-show="isToolbarCollapsed">
<button
@click="toggleDrawing('point')"
:class="{ active: drawingMode === 'point' }"
title="点"
class="min-tool-btn"
>
<span class="icon">📍</span>
</button>
<button
@click="toggleDrawing('line')"
:class="{ active: drawingMode === 'line' }"
title="线"
class="min-tool-btn"
>
<span class="icon">📏</span>
</button>
<button
@click="toggleDrawing('polygon')"
:class="{ active: drawingMode === 'polygon' }"
title="面"
class="min-tool-btn"
>
<span class="icon">🔶</span>
</button>
<button
@click="toggleDrawing('rectangle')"
:class="{ active: drawingMode === 'rectangle' }"
title="矩形"
class="min-tool-btn"
>
<span class="icon">⬜</span>
</button>
<button
@click="toggleDrawing('circle')"
:class="{ active: drawingMode === 'circle' }"
title="圆形"
class="min-tool-btn"
>
<span class="icon">⭕</span>
</button>
<div class="min-tool-separator"></div>
<button
@click="clearAll"
:disabled="!allEntities.length"
title="清除所有"
class="min-tool-btn danger"
>
<span class="icon">🗑️</span>
</button>
<button
@click="exportData"
:disabled="!allEntities.length"
title="导出数据"
class="min-tool-btn success"
>
<span class="icon">💾</span>
</button>
</div>
</div>
<!-- 测量结果显示 -->
<div class="measurement-panel" v-if="measurementResult">
<div class="measurement-content">
<h5>测量结果</h5>
<div class="measurement-item" v-if="measurementResult.distance">
<span>长度:</span>
<strong>{{ measurementResult.distance.toFixed(2) }} 米</strong>
</div>
<div class="measurement-item" v-if="measurementResult.area">
<span>面积:</span>
<strong>{{ measurementResult.area.toFixed(2) }} 平方米</strong>
</div>
<div class="measurement-item" v-if="measurementResult.radius">
<span>半径:</span>
<strong>{{ measurementResult.radius.toFixed(2) }} 米</strong>
</div>
<button @click="measurementResult = null" class="close-btn">关闭</button>
</div>
</div>
</div>
</template>
<script>
// import * as Cesium from 'cesium'
// import 'cesium/Build/Cesium/Widgets/widgets.css'
export default {
name: 'CesiumMap',
props: {
drawDomClick: {
type: Boolean,
default: false,
// 增加 props 类型校验,方便调试
validator(val) {
const isBoolean = typeof val === 'boolean'
if (!isBoolean) {
console.error('drawDomClick 必须是布尔值,当前值:', val, '类型:', typeof val)
}
return isBoolean
}
},
},
watch: {
drawDomClick: {
immediate: true, // 组件初始化时立即执行一次
handler(newVal, oldVal) {
// 可选:如果需要在值变化时执行额外逻辑(比如初始化地图)
if (newVal) {
// this.initMap()
}
}
}
},
data() {
return {
viewer: null,
scaleBar: null,
isToolbarCollapsed: false,
// 绘制相关
drawingMode: null, // 'point', 'line', 'polygon', 'rectangle', 'circle'
drawingHandler: null,
tempEntity: null, // 最终实体
tempPreviewEntity: null, // 预览实体(新增)
drawingPoints: [],
drawingStartPoint: null,
isDrawing: false,
// 实体管理
allEntities: [], // 所有绘制的实体
entityCounter: 0,
selectedEntity: null, // 当前选中的实体
// 测量结果
measurementResult: null,
// 默认样式
defaultStyles: {
point: { color: '#FF0000', size: 12 },
line: { color: '#00FF00', width: 3 },
polygon: { color: '#0000FF', opacity: 0.5, width: 2 },
rectangle: { color: '#FFA500', opacity: 0.3, width: 2 },
circle: { color: '#800080', opacity: 0.4, width: 2 }
}
}
},
mounted() {
console.log(this.drawDomClick,999999)
// this.initMap()
this.checkCesiumLoaded()
},
beforeDestroy() {
this.destroyViewer()
},
methods: {
// 切换工具栏展开/收起
toggleToolbar() {
this.isToolbarCollapsed = !this.isToolbarCollapsed
},
checkCesiumLoaded() {
if (typeof Cesium === 'undefined') {
console.error('Cesium未加载,请检查CDN链接');
// 可以设置重试机制
setTimeout(() => {
if (typeof Cesium !== 'undefined') {
this.initMap();
} else {
console.error('Cesium加载失败');
}
}, 1000);
} else {
this.initMap();
}
},
initMap() {
try {
// 确保 Cesium 已加载
// Cesium.buildModuleUrl.setBaseUrl(window.CESIUM_BASE_URL)
Cesium.Ion.defaultAccessToken = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJqdGkiOiJjN2MzMmE5OS01NGU3LTQzOGQtYjdjZi1mNGIwZTFjZjQ0NmEiLCJpZCI6MTQ0MDc2LCJpYXQiOjE2ODU3NjY1OTN9.iCmFY-5WNdvyAT-EO2j-unrFm4ZN9J6aSuB2wElQZ-I'
this.viewer = new Cesium.Viewer('cesiumViewer', {
animation: false,
fullscreenButton: false,
baseLayerPicker: false,
navigationInstructionsInitiallyVisible: false,
geocoder: false,
homeButton: false,
infoBox: false,
sceneModePicker: false,
selectionIndicator: false,
timeline: false,
navigationHelpButton: false,
sceneMode: Cesium.SceneMode.SCENE2D,
mapProjection: new Cesium.WebMercatorProjection(),
imageryProvider: false,
terrainProvider: new Cesium.EllipsoidTerrainProvider(),
baseLayer: false
})
this.viewer.cesiumWidget.creditContainer.style.display = "none"
this.loadOfflineMap()
this.setup2DConstraints()
this.viewer.camera.setView({
destination: Cesium.Cartesian3.fromDegrees(116.3974, 39.9093, 5000000),
orientation: {
heading: 0,
pitch: -Cesium.Math.PI_OVER_TWO,
roll: 0
}
})
this.initScaleBar()
console.log('Cesium离线二维地图已加载')
} catch (error) {
console.error('地图错误:', error)
// 如果Cesium加载失败,显示错误信息
this.showErrorMessage();
}
},
showErrorMessage() {
const container = document.getElementById('cesiumViewer');
if (container) {
container.innerHTML = `
<div style="
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background: #f5f5f5;
color: #666;
font-family: Arial, sans-serif;
">
<h3 style="margin-bottom: 20px;">地图加载失败</h3>
<p>可能的原因:</p>
<ul style="text-align: left; margin: 10px 0;">
<li>网络连接问题</li>
<li>Cesium CDN资源未加载</li>
<li>浏览器兼容性问题</li>
</ul>
<button onclick="location.reload()" style="
margin-top: 20px;
padding: 10px 20px;
background: #4dabf7;
color: white;
border: none;
border-radius: 4px;
cursor: pointer;
">
重新加载
</button>
</div>
`;
}
},
setup2DConstraints() {
const scene = this.viewer.scene
const controller = scene.screenSpaceCameraController
controller.enableTilt = false
controller.enableRotate = false
controller.enableLook = false
scene.screenSpaceCameraController.maximumPitch = 0
scene.screenSpaceCameraController.minimumPitch = 0
},
loadOfflineMap() {
this.viewer.imageryLayers.removeAll()
try {
const offlineProvider = new Cesium.UrlTemplateImageryProvider({
url: 'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}',
minimumLevel: 0,
maximumLevel: 18,
tileWidth: 256,
tileHeight: 256,
tilingScheme: new Cesium.WebMercatorTilingScheme(),
credit: '离线地图'
})
this.viewer.imageryLayers.addImageryProvider(offlineProvider)
} catch (error) {
console.error('加载离线地图失败:', error)
this.showGridLayer()
}
},
showGridLayer() {
const gridProvider = new Cesium.GridImageryProvider()
this.viewer.imageryLayers.addImageryProvider(gridProvider)
this.addCoordinateLabels()
},
addCoordinateLabels() {
for (let lon = -180; lon <= 180; lon += 30) {
for (let lat = -90; lat <= 90; lat += 30) {
this.viewer.entities.add({
position: Cesium.Cartesian3.fromDegrees(lon, lat),
label: {
text: `${lat}°N\n${lon}°E`,
font: '12px sans-serif',
fillColor: Cesium.Color.BLACK,
outlineColor: Cesium.Color.WHITE,
outlineWidth: 2,
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
verticalOrigin: Cesium.VerticalOrigin.CENTER,
pixelOffset: new Cesium.Cartesian2(10, 0)
}
})
}
}
},
// ================== 绘制功能 ==================
toggleDrawing(mode) {
if (this.drawingMode === mode) {
// 停止当前绘制
this.stopDrawing()
this.drawingMode = null
} else {
// 停止之前的绘制
if (this.drawingMode) {
this.stopDrawing()
}
// 开始新的绘制
this.drawingMode = mode
this.startDrawing(mode)
}
},
startDrawing(mode) {
this.stopDrawing()
this.drawingPoints = []
this.drawingStartPoint = null
this.isDrawing = true
this.drawingHandler = new Cesium.ScreenSpaceEventHandler(this.viewer.scene.canvas)
// 根据模式设置不同的鼠标样式和事件
switch(mode) {
case 'point':
this.startPointDrawing()
break
case 'line':
this.startLineDrawing()
break
case 'polygon':
this.startPolygonDrawing()
break
case 'rectangle':
this.startRectangleDrawing()
break
case 'circle':
this.startCircleDrawing()
break
}
this.viewer.scene.canvas.style.cursor = 'crosshair'
console.log(`开始绘制 ${this.getTypeName(mode)}`)
},
stopDrawing() {
if (this.drawingHandler) {
this.drawingHandler.destroy();
this.drawingHandler = null;
}
if (this.tempEntity) {
this.viewer.entities.remove(this.tempEntity);
this.tempEntity = null;
}
// 确保也清理预览实体
if (this.tempPreviewEntity) {
this.viewer.entities.remove(this.tempPreviewEntity);
this.tempPreviewEntity = null;
}
this.drawingPoints = [];
this.drawingStartPoint = null;
this.isDrawing = false;
this.viewer.scene.canvas.style.cursor = 'default';
},
// ********************************************************************
// 绘制点
startPointDrawing() {
this.drawingHandler.setInputAction((click) => {
const position = this.getClickPosition(click.position)
if (position) {
const { lat, lng } = this.cartesianToLatLng(position)
this.addPointEntity(lat, lng)
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK)
},
// 绘制线
startLineDrawing() {
this.drawingPoints = [];
// 鼠标点击事件 - 添加点
this.drawingHandler.setInputAction((click) => {
const position = this.getClickPosition(click.position);
if (position) {
this.drawingPoints.push(position);
// 如果这是第一个点,创建一个与自身连接的线段(看起来像一个点)
if (this.drawingPoints.length === 1) {
const previewPositions = [position, position];
if (this.tempPreviewEntity) {
this.viewer.entities.remove(this.tempPreviewEntity);
}
this.tempPreviewEntity = this.viewer.entities.add({
polyline: {
positions: previewPositions,
width: this.defaultStyles.line.width,
material: Cesium.Color.fromCssColorString(this.defaultStyles.line.color).withAlpha(0.5),
clampToGround: true
}
});
}
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);
// 鼠标移动事件 - 实时更新预览线段
this.drawingHandler.setInputAction((movement) => {
if (this.drawingPoints.length > 0) {
const mousePosition = this.getClickPosition(movement.endPosition);
if (mousePosition) {
// 创建包含当前鼠标位置的预览线段
const previewPositions = [...this.drawingPoints, mousePosition];
// 如果已有预览线段,先移除
if (this.tempPreviewEntity) {
this.viewer.entities.remove(this.tempPreviewEntity);
}
// 创建预览线段(半透明效果),显示实时轨迹
this.tempPreviewEntity = this.viewer.entities.add({
polyline: {
positions: previewPositions,
width: this.defaultStyles.line.width,
material: Cesium.Color.fromCssColorString(this.defaultStyles.line.color).withAlpha(0.5), // 半透明效果
clampToGround: true
}
});
}
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);
// 右键完成绘制
this.drawingHandler.setInputAction(() => {
if (this.drawingPoints.length > 1) {
this.finishLineDrawing();
} else {
this.cancelDrawing();
}
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK);
},
finishLineDrawing() {
// 将预览线段转换为最终线段
if (this.drawingPoints.length > 1) {
// 移除预览线段
if (this.tempPreviewEntity) {
this.viewer.entities.remove(this.tempPreviewEntity);
this.tempPreviewEntity = null;
}
// 创建最终的实线实体
const entity = this.addLineEntity([...this.drawingPoints]);
// 计算长度
const length = this.calculateLineLength([...this.drawingPoints]);
this.measurementResult = {
distance: length,
type: 'line'
};
this.stopDrawing();
return entity;
} else {
this.cancelDrawing();
return null;
}
},
// 绘制多边形
startPolygonDrawing() {
this.drawingHandler.setInputAction((click) => {
const position = this.getClickPosition(click.position)
if (position) {
this.drawingPoints.push(position)
// 更新临时多边形
if (this.tempEntity) {
this.viewer.entities.remove(this.tempEntity)
}
if (this.drawingPoints.length > 2) {
// 闭合多边形
const polygonPoints = [...this.drawingPoints, this.drawingPoints[0]]
this.tempEntity = this.viewer.entities.add({
polygon: {
hierarchy: new Cesium.PolygonHierarchy(polygonPoints),
material: Cesium.Color.fromCssColorString(this.defaultStyles.polygon.color)
.withAlpha(this.defaultStyles.polygon.opacity),
outline: true,
outlineColor: Cesium.Color.fromCssColorString(this.defaultStyles.polygon.color),
outlineWidth: this.defaultStyles.polygon.width
}
})
} else if (this.drawingPoints.length === 2) {
// 只有两个点时显示线
this.tempEntity = this.viewer.entities.add({
polyline: {
positions: this.drawingPoints,
width: this.defaultStyles.polygon.width,
material: Cesium.Color.fromCssColorString(this.defaultStyles.polygon.color),
clampToGround: true
}
})
} else if (this.drawingPoints.length === 1) {
// 只有一个点时显示点
this.tempEntity = this.viewer.entities.add({
position: this.drawingPoints[0],
point: {
pixelSize: 8,
color: Cesium.Color.fromCssColorString(this.defaultStyles.polygon.color)
}
})
}
}
}, Cesium.ScreenSpaceEventType.LEFT_CLICK)
// 双击完成绘制
this.drawingHandler.setInputAction((click) => {
if (this.drawingPoints.length > 2) {
this.finishPolygonDrawing()
}
}, Cesium.ScreenSpaceEventType.LEFT_DOUBLE_CLICK)
// 右键取消
this.drawingHandler.setInputAction(() => {
this.cancelDrawing()
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK)
},
finishPolygonDrawing() {
const positions = [...this.drawingPoints]
const entity = this.addPolygonEntity(positions)
// 计算面积
const area = this.calculatePolygonArea(positions)
this.measurementResult = {
area: area,
type: 'polygon'
}
this.stopDrawing()
return entity
},
// 绘制矩形
startRectangleDrawing() {
let startPosition = null
this.drawingHandler.setInputAction((click) => {
startPosition = this.getClickPosition(click.position)
if (startPosition) {
this.drawingStartPoint = startPosition
}
}, Cesium.ScreenSpaceEventType.LEFT_DOWN)
this.drawingHandler.setInputAction((movement) => {
if (startPosition) {
const endPosition = this.getClickPosition(movement.endPosition)
if (endPosition) {
// 更新临时矩形
if (this.tempEntity) {
this.viewer.entities.remove(this.tempEntity)
}
const rectangle = this.calculateRectangle(startPosition, endPosition)
this.tempEntity = this.viewer.entities.add({
rectangle: {
coordinates: rectangle,
material: Cesium.Color.fromCssColorString(this.defaultStyles.rectangle.color)
.withAlpha(this.defaultStyles.rectangle.opacity),
outline: true,
outlineColor: Cesium.Color.fromCssColorString(this.defaultStyles.rectangle.color),
outlineWidth: this.defaultStyles.rectangle.width
}
})
}
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE)
this.drawingHandler.setInputAction(() => {
if (startPosition && this.tempEntity) {
this.finishRectangleDrawing(startPosition)
startPosition = null
}
}, Cesium.ScreenSpaceEventType.LEFT_UP)
// 右键取消
this.drawingHandler.setInputAction(() => {
this.cancelDrawing()
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK)
},
finishRectangleDrawing(startPosition) {
if (!this.tempEntity) return
const rectangleCoords = this.tempEntity.rectangle.coordinates.getValue()
const entity = this.addRectangleEntity(rectangleCoords)
// 计算面积
const area = this.calculateRectangleArea(rectangleCoords)
this.measurementResult = {
area: area,
type: 'rectangle'
}
this.stopDrawing()
return entity
},
// 绘制圆形
startCircleDrawing() {
let centerPosition = null
let radius = 0
this.drawingHandler.setInputAction((click) => {
centerPosition = this.getClickPosition(click.position)
if (centerPosition) {
this.drawingStartPoint = centerPosition
// 创建中心点
if (this.tempEntity) {
this.viewer.entities.remove(this.tempEntity)
}
this.tempEntity = this.viewer.entities.add({
position: centerPosition,
point: {
pixelSize: 8,
color: Cesium.Color.fromCssColorString(this.defaultStyles.circle.color)
}
})
}
}, Cesium.ScreenSpaceEventType.LEFT_DOWN)
this.drawingHandler.setInputAction((movement) => {
if (centerPosition) {
const currentPosition = this.getClickPosition(movement.endPosition)
if (currentPosition) {
radius = Cesium.Cartesian3.distance(centerPosition, currentPosition)
// 更新临时圆形
if (this.tempEntity) {
this.viewer.entities.remove(this.tempEntity)
}
// 创建圆形多边形
const positions = this.calculateCircle(centerPosition, radius, 64)
this.tempEntity = this.viewer.entities.add({
polygon: {
hierarchy: new Cesium.PolygonHierarchy(positions),
material: Cesium.Color.fromCssColorString(this.defaultStyles.circle.color)
.withAlpha(this.defaultStyles.circle.opacity),
outline: true,
outlineColor: Cesium.Color.fromCssColorString(this.defaultStyles.circle.color),
outlineWidth: this.defaultStyles.circle.width
}
})
}
}
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE)
this.drawingHandler.setInputAction(() => {
if (centerPosition && radius > 0) {
this.finishCircleDrawing(centerPosition, radius)
centerPosition = null
radius = 0
}
}, Cesium.ScreenSpaceEventType.LEFT_UP)
// 右键取消
// this.drawingHandler.setInputAction(() => {
// this.cancelDrawing()
// }, Cesium.ScreenSpaceEventType.RIGHT_CLICK)
},
finishCircleDrawing(center, radius) {
const positions = this.calculateCircle(center, radius, 64)
const entity = this.addCircleEntity(center, radius, positions)
// 计算面积
const area = Math.PI * radius * radius
this.measurementResult = {
area: area,
radius: radius,
type: 'circle'
}
this.stopDrawing()
return entity
},
cancelDrawing() {
this.stopDrawing()
this.drawingMode = null
},
// ================== 实体创建方法 ==================
addPointEntity(lat, lng) {
this.entityCounter++
const id = `point_${this.entityCounter}`
const entity = this.viewer.entities.add({
id: id,
name: `${this.entityCounter}`,
position: Cesium.Cartesian3.fromDegrees(lng, lat),
point: {
pixelSize: this.defaultStyles.point.size,
color: Cesium.Color.fromCssColorString(this.defaultStyles.point.color),
outlineColor: Cesium.Color.WHITE,
outlineWidth: 2
},
label: {
text: `${this.entityCounter}`,
font: '14px Arial',
fillColor: Cesium.Color.WHITE,
outlineColor: Cesium.Color.BLACK,
outlineWidth: 2,
style: Cesium.LabelStyle.FILL_AND_OUTLINE,
verticalOrigin: Cesium.VerticalOrigin.CENTER,
horizontalOrigin: Cesium.HorizontalOrigin.CENTER
}
})
const entityData = {
id,
type: 'point',
lat,
lng,
entity: entity,
color: this.defaultStyles.point.color,
size: this.defaultStyles.point.size,
label: `${this.entityCounter}`
}
this.allEntities.push(entityData)
// 添加点击事件
entity.clickHandler = (e) => {
this.selectEntity(entityData)
e.stopPropagation()
}
return entityData
},
addLineEntity(positions) {
this.entityCounter++
const id = `line_${this.entityCounter}`
const entity = this.viewer.entities.add({
id: id,
name: `线 ${this.entityCounter}`,
polyline: {
positions: positions,
width: this.defaultStyles.line.width,
material: Cesium.Color.fromCssColorString(this.defaultStyles.line.color),
clampToGround: true
}
})
const entityData = {
id,
type: 'line',
points: positions.map(p => this.cartesianToLatLng(p)),
positions: positions,
entity: entity,
color: this.defaultStyles.line.color,
width: this.defaultStyles.line.width,
label: `线 ${this.entityCounter}`
}
this.allEntities.push(entityData)
entity.clickHandler = (e) => {
this.selectEntity(entityData)
e.stopPropagation()
}
return entityData
},
addPolygonEntity(positions) {
this.entityCounter++
const id = `polygon_${this.entityCounter}`
// 闭合多边形
const polygonPositions = [...positions, positions[0]]
const entity = this.viewer.entities.add({
id: id,
name: `${this.entityCounter}`,
polygon: {
hierarchy: new Cesium.PolygonHierarchy(polygonPositions),
material: Cesium.Color.fromCssColorString(this.defaultStyles.polygon.color)
.withAlpha(this.defaultStyles.polygon.opacity),
outline: true,
outlineColor: Cesium.Color.fromCssColorString(this.defaultStyles.polygon.color),
outlineWidth: this.defaultStyles.polygon.width
}
})
const entityData = {
id,
type: 'polygon',
points: positions.map(p => this.cartesianToLatLng(p)),
positions: polygonPositions,
entity: entity,
color: this.defaultStyles.polygon.color,
opacity: this.defaultStyles.polygon.opacity,
width: this.defaultStyles.polygon.width,
label: `${this.entityCounter}`
}
this.allEntities.push(entityData)
entity.clickHandler = (e) => {
this.selectEntity(entityData)
e.stopPropagation()
}
return entityData
},
addRectangleEntity(coordinates) {
this.entityCounter++
const id = `rectangle_${this.entityCounter}`
const entity = this.viewer.entities.add({
id: id,
name: `矩形 ${this.entityCounter}`,
rectangle: {
coordinates: coordinates,
material: Cesium.Color.fromCssColorString(this.defaultStyles.rectangle.color)
.withAlpha(this.defaultStyles.rectangle.opacity),
outline: true,
outlineColor: Cesium.Color.fromCssColorString(this.defaultStyles.rectangle.color),
outlineWidth: this.defaultStyles.rectangle.width
}
})
const entityData = {
id,
type: 'rectangle',
coordinates: coordinates,
entity: entity,
color: this.defaultStyles.rectangle.color,
opacity: this.defaultStyles.rectangle.opacity,
width: this.defaultStyles.rectangle.width,
label: `矩形 ${this.entityCounter}`
}
this.allEntities.push(entityData)
entity.clickHandler = (e) => {
this.selectEntity(entityData)
e.stopPropagation()
}
return entityData
},
addCircleEntity(center, radius, positions) {
this.entityCounter++
const id = `circle_${this.entityCounter}`
const entity = this.viewer.entities.add({
id: id,
name: `圆形 ${this.entityCounter}`,
polygon: {
hierarchy: new Cesium.PolygonHierarchy(positions),
material: Cesium.Color.fromCssColorString(this.defaultStyles.circle.color)
.withAlpha(this.defaultStyles.circle.opacity),
outline: true,
outlineColor: Cesium.Color.fromCssColorString(this.defaultStyles.circle.color),
outlineWidth: this.defaultStyles.circle.width
},
position: center,
point: {
pixelSize: 5,
color: Cesium.Color.RED
}
})
const centerLL = this.cartesianToLatLng(center)
const entityData = {
id,
type: 'circle',
center: centerLL,
radius: radius,
positions: positions,
entity: entity,
color: this.defaultStyles.circle.color,
opacity: this.defaultStyles.circle.opacity,
width: this.defaultStyles.circle.width,
label: `圆形 ${this.entityCounter}`
}
this.allEntities.push(entityData)
entity.clickHandler = (e) => {
this.selectEntity(entityData)
e.stopPropagation()
}
return entityData
},
// ================== 工具方法 ==================
getClickPosition(pixelPosition) {
const cartesian = this.viewer.camera.pickEllipsoid(pixelPosition, this.viewer.scene.globe.ellipsoid)
return cartesian
},
cartesianToLatLng(cartesian) {
const cartographic = Cesium.Cartographic.fromCartesian(cartesian)
return {
lat: Cesium.Math.toDegrees(cartographic.latitude),
lng: Cesium.Math.toDegrees(cartographic.longitude)
}
},
calculateRectangle(start, end) {
const startLL = this.cartesianToLatLng(start)
const endLL = this.cartesianToLatLng(end)
return Cesium.Rectangle.fromDegrees(
Math.min(startLL.lng, endLL.lng),
Math.min(startLL.lat, endLL.lat),
Math.max(startLL.lng, endLL.lng),
Math.max(startLL.lat, endLL.lat)
)
},
calculateCircle(center, radius, segments) {
const positions = []
const centerLL = Cesium.Cartographic.fromCartesian(center)
for (let i = 0; i < segments; i++) {
const angle = (i / segments) * Math.PI * 2
const lat = centerLL.latitude + (radius / 6378137) * Math.sin(angle)
const lon = centerLL.longitude + (radius / 6378137) * Math.cos(angle) / Math.cos(centerLL.latitude)
positions.push(Cesium.Cartesian3.fromRadians(lon, lat))
}
return positions
},
calculateLineLength(positions) {
let totalLength = 0
for (let i = 0; i < positions.length - 1; i++) {
totalLength += Cesium.Cartesian3.distance(positions[i], positions[i + 1])
}
return totalLength
},
calculatePolygonArea(positions) {
if (positions.length < 3) return 0
let area = 0
const n = positions.length
for (let i = 0; i < n; i++) {
const j = (i + 1) % n
const p1 = Cesium.Cartographic.fromCartesian(positions[i])
const p2 = Cesium.Cartographic.fromCartesian(positions[j])
area += p1.longitude * p2.latitude - p2.longitude * p1.latitude
}
area = Math.abs(area) * 6378137 * 6378137 / 2
return area
},
calculateRectangleArea(coordinates) {
const rect = coordinates.getValue ? coordinates.getValue() : coordinates
const width = Cesium.Cartesian3.distance(
Cesium.Cartesian3.fromRadians(rect.west, rect.north),
Cesium.Cartesian3.fromRadians(rect.east, rect.north)
)
const height = Cesium.Cartesian3.distance(
Cesium.Cartesian3.fromRadians(rect.west, rect.north),
Cesium.Cartesian3.fromRadians(rect.west, rect.south)
)
return width * height
},
// ================== 实体管理 ==================
selectEntity(entity) {
this.selectedEntity = entity
// 居中显示
if (entity.type === 'point') {
this.viewer.camera.flyTo({
destination: Cesium.Cartesian3.fromDegrees(entity.lng, entity.lat, 1000),
duration: 1.0
})
} else if (entity.positions && entity.positions.length > 0) {
const rectangle = Cesium.Rectangle.fromCartographicArray(
entity.positions.map(p => Cesium.Cartographic.fromCartesian(p))
)
this.viewer.camera.flyTo({
destination: rectangle,
duration: 1.0
})
}
},
updateEntityStyle() {
if (!this.selectedEntity || !this.selectedEntity.entity) return
const entity = this.selectedEntity.entity
const data = this.selectedEntity
switch(data.type) {
case 'point':
entity.point.color = Cesium.Color.fromCssColorString(data.color)
entity.point.pixelSize = data.size
break
case 'line':
entity.polyline.material = Cesium.Color.fromCssColorString(data.color)
entity.polyline.width = data.width
break
case 'polygon':
entity.polygon.material = Cesium.Color.fromCssColorString(data.color).withAlpha(data.opacity)
entity.polygon.outlineColor = Cesium.Color.fromCssColorString(data.color)
entity.polygon.outlineWidth = data.width
break
case 'rectangle':
entity.rectangle.material = Cesium.Color.fromCssColorString(data.color).withAlpha(data.opacity)
entity.rectangle.outlineColor = Cesium.Color.fromCssColorString(data.color)
entity.rectangle.outlineWidth = data.width
break
case 'circle':
entity.polygon.material = Cesium.Color.fromCssColorString(data.color).withAlpha(data.opacity)
entity.polygon.outlineColor = Cesium.Color.fromCssColorString(data.color)
entity.polygon.outlineWidth = data.width
break
}
},
updateEntityLabel() {
if (!this.selectedEntity || !this.selectedEntity.entity) return
const entity = this.selectedEntity.entity
entity.name = this.selectedEntity.label
// 如果是点,更新标签
if (this.selectedEntity.type === 'point') {
entity.label.text = this.selectedEntity.label
}
},
deleteSelectedEntity() {
if (this.selectedEntity) {
this.removeEntity(this.selectedEntity.id)
this.selectedEntity = null
}
},
removeEntity(id) {
const index = this.allEntities.findIndex(e => e.id === id)
if (index > -1) {
const entity = this.allEntities[index]
// 从地图中移除
if (entity.entity) {
this.viewer.entities.remove(entity.entity)
}
// 从数组中移除
this.allEntities.splice(index, 1)
// 如果删除的是选中的实体,清空选中状态
if (this.selectedEntity && this.selectedEntity.id === id) {
this.selectedEntity = null
}
}
},
clearAll() {
// 停止绘制
this.stopDrawing()
this.drawingMode = null
// 移除所有实体
this.allEntities.forEach(entity => {
if (entity.entity) {
this.viewer.entities.remove(entity.entity)
}
})
// 清空数组
this.allEntities = []
this.entityCounter = 0
this.selectedEntity = null
this.measurementResult = null
console.log('已清除所有图形')
},
// ================== 其他方法 ==================
getTypeName(type) {
const names = {
point: '点',
line: '线',
polygon: '面',
rectangle: '矩形',
circle: '圆形'
}
return names[type] || type
},
exportData() {
const data = {
version: '1.0',
date: new Date().toISOString(),
entities: this.allEntities.map(entity => ({
id: entity.id,
type: entity.type,
label: entity.label,
color: entity.color,
data: entity.type === 'point' ? {
lat: entity.lat,
lng: entity.lng
} : entity.type === 'line' || entity.type === 'polygon' ? {
points: entity.points
} : entity.type === 'rectangle' ? {
coordinates: entity.coordinates
} : {
center: entity.center,
radius: entity.radius
}
}))
}
const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' })
const url = URL.createObjectURL(blob)
const a = document.createElement('a')
a.href = url
a.download = `cesium-drawing-${new Date().toISOString().slice(0, 10)}.json`
a.click()
URL.revokeObjectURL(url)
console.log('数据已导出', data)
},
initScaleBar() {
// ... 原有的比例尺代码保持不变
},
updateScaleBar() {
// ... 原有的比例尺更新代码保持不变
},
destroyViewer() {
this.stopDrawing()
this.clearAll()
if (this.viewer) {
this.viewer.destroy()
this.viewer = null
}
}
}
}
</script>
<style scoped>
.cesium-container {
width: 100vw;
height: 100vh;
position: relative;
}
#cesiumViewer {
width: 100%;
height: 100%;
}
/* 主工具栏 - 修改后 */
.main-toolbar {
position: absolute;
top: 365px; /* 增大这个数值即可整体下移,比如从100px改成150px */
left: 62px;
z-index: 1000;
display: flex;
background: rgba(255, 255, 255, 0.95);
border-radius: 12px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
transition: all 0.3s ease;
}
.main-toolbar.collapsed {
background: rgba(255, 255, 255, 0.9);
}
/* 工具栏内容 - 无需修改 */
.toolbar-content {
display: flex;
flex-direction: column;
gap: 15px;
padding: 15px;
min-width: 180px;
}
/* 收起按钮 */
.collapse-btn {
display: flex;
align-items: center;
padding: 10px;
cursor: pointer;
background: #f8f9fa;
border-top-left-radius: 12px;
border-bottom-left-radius: 12px;
border-right: 1px solid #e9ecef;
transition: all 0.3s ease;
user-select: none;
}
.collapse-btn:hover {
background: #e9ecef;
}
.collapse-icon {
font-size: 14px;
font-weight: bold;
color: #495057;
margin-right: 5px;
}
.collapse-text {
font-size: 12px;
color: #6c757d;
white-space: nowrap;
}
/* 工具栏组 */
.toolbar-group {
display: flex;
flex-direction: column;
gap: 8px;
padding-bottom: 10px;
border-bottom: 1px solid #e9ecef;
}
.toolbar-group:last-child {
border-bottom: none;
padding-bottom: 0;
}
/* 收起时的最小化工具栏 */
.toolbar-minimized {
display: flex;
flex-direction: column;
padding: 10px 8px;
gap: 8px;
}
.min-tool-btn {
width: 36px;
height: 36px;
border: 2px solid #e9ecef;
border-radius: 8px;
background: white;
cursor: pointer;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
color: #495057;
transition: all 0.3s ease;
}
.min-tool-btn:hover {
background: #f8f9fa;
border-color: #4dabf7;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.min-tool-btn.active {
background: #4dabf7;
color: white;
border-color: #4dabf7;
}
.min-tool-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none !important;
}
.min-tool-btn.danger {
color: #dc3545;
}
.min-tool-btn.danger:hover {
background: #f8d7da;
border-color: #dc3545;
}
.min-tool-btn.success {
color: #28a745;
}
.min-tool-btn.success:hover {
background: #d4edda;
border-color: #28a745;
}
.min-tool-separator {
height: 1px;
background: #e9ecef;
margin: 5px 0;
}
/* 原来的工具按钮样式保持不变,但添加展开/收起状态的调整 */
.tool-btn {
padding: 10px 15px;
background: white;
border: 2px solid #e9ecef;
border-radius: 8px;
cursor: pointer;
display: flex;
align-items: center;
gap: 10px;
font-size: 14px;
font-weight: 500;
color: #495057;
transition: all 0.3s ease;
min-width: 120px;
}
.tool-btn:hover {
background: #f8f9fa;
border-color: #4dabf7;
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.tool-btn.active {
background: #4dabf7;
color: white;
border-color: #4dabf7;
}
.tool-btn:disabled {
opacity: 0.5;
cursor: not-allowed;
transform: none !important;
}
.tool-btn.danger {
color: #dc3545;
}
.tool-btn.danger:hover {
background: #f8d7da;
border-color: #dc3545;
}
.tool-btn.success {
color: #28a745;
}
.tool-btn.success:hover {
background: #d4edda;
border-color: #28a745;
}
.tool-btn .icon {
font-size: 16px;
width: 20px;
text-align: center;
}
.tool-btn .text {
white-space: nowrap;
}
/* 其他样式保持不变... */
/* 属性面板 */
.property-panel {
position: absolute;
top: 20px;
left: 20px;
z-index: 1000;
background: rgba(255, 255, 255, 0.98);
border-radius: 12px;
padding: 20px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
min-width: 250px;
max-width: 300px;
}
/* 测量结果面板 */
.measurement-panel {
position: absolute;
bottom: 80px;
left: 20px;
z-index: 1000;
background: rgba(255, 255, 255, 0.98);
border-radius: 12px;
padding: 15px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
min-width: 200px;
}
/* 自定义比例尺样式 */
:deep(.scale-bar-container) {
user-select: none;
pointer-events: none;
}
/* 隐藏Cesium的默认控件 */
:deep(.cesium-viewer-bottom) {
display: none !important;
}
:deep(.cesium-credit-logoContainer) {
display: none !important;
}
:deep(.cesium-credit-textContainer) {
display: none !important;
}
/* 响应式调整 */
@media (max-width: 768px) {
.main-toolbar {
right: 10px;
top: 80px;
}
.toolbar-content {
min-width: 150px;
}
.tool-btn {
min-width: 100px;
padding: 8px 12px;
}
}
</style>