|
|
|
@ -26,6 +26,9 @@ import * as Cesium from 'cesium' |
|
|
|
import 'cesium/Build/Cesium/Widgets/widgets.css' |
|
|
|
import DrawingToolbar from './DrawingToolbar.vue' |
|
|
|
import MeasurementPanel from './MeasurementPanel.vue' |
|
|
|
import axios from 'axios' |
|
|
|
import request from '@/utils/request' |
|
|
|
import { getToken } from '@/utils/auth' |
|
|
|
export default { |
|
|
|
name: 'CesiumMap', |
|
|
|
props: { |
|
|
|
@ -81,7 +84,11 @@ export default { |
|
|
|
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 } |
|
|
|
circle: { color: '#800080', opacity: 0.4, width: 2 }, |
|
|
|
sector: { color: '#FF6347', opacity: 0.5, width: 2 }, |
|
|
|
arrow: { color: '#FF0000', width: 6 }, |
|
|
|
text: { color: '#000000', font: '32px Microsoft YaHei, PingFang SC, sans-serif', backgroundColor: 'rgba(255, 255, 255, 0.8)' }, |
|
|
|
image: { width: 100, height: 100 } |
|
|
|
} |
|
|
|
} |
|
|
|
}, |
|
|
|
@ -304,6 +311,18 @@ export default { |
|
|
|
case 'circle': |
|
|
|
this.startCircleDrawing() |
|
|
|
break |
|
|
|
case 'sector': |
|
|
|
this.startSectorDrawing() |
|
|
|
break |
|
|
|
case 'arrow': |
|
|
|
this.startArrowDrawing() |
|
|
|
break |
|
|
|
case 'text': |
|
|
|
this.startTextDrawing() |
|
|
|
break |
|
|
|
case 'image': |
|
|
|
this.startImageDrawing() |
|
|
|
break |
|
|
|
} |
|
|
|
|
|
|
|
this.viewer.scene.canvas.style.cursor = 'crosshair' |
|
|
|
@ -812,6 +831,602 @@ export default { |
|
|
|
this.stopDrawing(); |
|
|
|
}, |
|
|
|
|
|
|
|
// 绘制扇形 |
|
|
|
startSectorDrawing() { |
|
|
|
this.drawingPoints = []; // 存储圆心、半径端点、角度端点 |
|
|
|
let activeCursorPosition = null; // 实时鼠标位置 |
|
|
|
let centerPoint = null; // 圆心坐标 |
|
|
|
let radiusPoint = null; // 半径端点 |
|
|
|
let radius = 0; // 半径长度 |
|
|
|
|
|
|
|
// 1. 清理旧实体 |
|
|
|
if (this.tempEntity) this.viewer.entities.remove(this.tempEntity); |
|
|
|
if (this.tempPreviewEntity) this.viewer.entities.remove(this.tempPreviewEntity); |
|
|
|
this.tempEntity = null; |
|
|
|
this.tempPreviewEntity = null; |
|
|
|
|
|
|
|
// 2. 鼠标移动事件 |
|
|
|
this.drawingHandler.setInputAction((movement) => { |
|
|
|
const newPosition = this.getClickPosition(movement.endPosition); |
|
|
|
if (newPosition) { |
|
|
|
activeCursorPosition = newPosition; |
|
|
|
} |
|
|
|
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE); |
|
|
|
|
|
|
|
// 3. 鼠标点击事件 |
|
|
|
this.drawingHandler.setInputAction((click) => { |
|
|
|
const position = this.getClickPosition(click.position); |
|
|
|
if (position) { |
|
|
|
// --- 情况A:第一次点击(确定圆心) --- |
|
|
|
if (!centerPoint) { |
|
|
|
centerPoint = position; |
|
|
|
this.drawingPoints.push(centerPoint); |
|
|
|
activeCursorPosition = centerPoint; |
|
|
|
|
|
|
|
// 创建动态预览半径线 |
|
|
|
this.tempPreviewEntity = this.viewer.entities.add({ |
|
|
|
polyline: { |
|
|
|
positions: new Cesium.CallbackProperty(() => { |
|
|
|
if (centerPoint && activeCursorPosition) { |
|
|
|
return [centerPoint, activeCursorPosition]; |
|
|
|
} |
|
|
|
return []; |
|
|
|
}, false), |
|
|
|
width: this.defaultStyles.sector.width, |
|
|
|
material: new Cesium.PolylineDashMaterialProperty({ |
|
|
|
color: Cesium.Color.fromCssColorString(this.defaultStyles.sector.color), |
|
|
|
dashLength: 16 |
|
|
|
}), |
|
|
|
clampToGround: true |
|
|
|
} |
|
|
|
}); |
|
|
|
} |
|
|
|
// --- 情况B:第二次点击(确定半径) --- |
|
|
|
else if (!radiusPoint) { |
|
|
|
radiusPoint = position; |
|
|
|
this.drawingPoints.push(radiusPoint); |
|
|
|
radius = Cesium.Cartesian3.distance(centerPoint, radiusPoint); |
|
|
|
|
|
|
|
// 移除半径预览线 |
|
|
|
if (this.tempPreviewEntity) { |
|
|
|
this.viewer.entities.remove(this.tempPreviewEntity); |
|
|
|
this.tempPreviewEntity = null; |
|
|
|
} |
|
|
|
|
|
|
|
// 创建动态预览扇形 |
|
|
|
this.tempEntity = this.viewer.entities.add({ |
|
|
|
polygon: { |
|
|
|
hierarchy: new Cesium.CallbackProperty(() => { |
|
|
|
if (centerPoint && activeCursorPosition) { |
|
|
|
const currentRadius = Cesium.Cartesian3.distance(centerPoint, activeCursorPosition); |
|
|
|
const startAngle = this.calculatePointAngle(centerPoint, radiusPoint); |
|
|
|
const endAngle = this.calculatePointAngle(centerPoint, activeCursorPosition); |
|
|
|
const positions = this.generateSectorPositions(centerPoint, currentRadius, startAngle, endAngle); |
|
|
|
return new Cesium.PolygonHierarchy(positions); |
|
|
|
} |
|
|
|
return new Cesium.PolygonHierarchy([]); |
|
|
|
}, false), |
|
|
|
material: Cesium.Color.fromCssColorString(this.defaultStyles.sector.color).withAlpha(0.5), |
|
|
|
outline: true, |
|
|
|
outlineColor: Cesium.Color.fromCssColorString(this.defaultStyles.sector.color), |
|
|
|
outlineWidth: this.defaultStyles.sector.width |
|
|
|
} |
|
|
|
}); |
|
|
|
} |
|
|
|
// --- 情况C:第三次点击(确定角度) --- |
|
|
|
else { |
|
|
|
const anglePoint = position; |
|
|
|
this.drawingPoints.push(anglePoint); |
|
|
|
activeCursorPosition = null; // 停止动态更新 |
|
|
|
|
|
|
|
// 传递角度点位置去结束绘制 |
|
|
|
this.finishSectorDrawing(centerPoint, radiusPoint, anglePoint); |
|
|
|
} |
|
|
|
} |
|
|
|
}, Cesium.ScreenSpaceEventType.LEFT_CLICK); |
|
|
|
|
|
|
|
// 4. 右键取消 |
|
|
|
this.drawingHandler.setInputAction(() => { |
|
|
|
this.cancelDrawing(); |
|
|
|
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK); |
|
|
|
}, |
|
|
|
|
|
|
|
// 完成扇形绘制 |
|
|
|
finishSectorDrawing(centerPoint, radiusPoint, anglePoint) { |
|
|
|
const radius = Cesium.Cartesian3.distance(centerPoint, anglePoint); |
|
|
|
const startAngle = this.calculatePointAngle(centerPoint, radiusPoint); |
|
|
|
const endAngle = this.calculatePointAngle(centerPoint, anglePoint); |
|
|
|
|
|
|
|
// 1. 移除动态预览实体 |
|
|
|
if (this.tempEntity) { |
|
|
|
this.viewer.entities.remove(this.tempEntity); |
|
|
|
this.tempEntity = null; |
|
|
|
} |
|
|
|
|
|
|
|
// 2. 生成扇形顶点 |
|
|
|
const positions = this.generateSectorPositions(centerPoint, radius, startAngle, endAngle); |
|
|
|
|
|
|
|
// 3. 创建最终显示的静态实体 |
|
|
|
const finalEntity = this.viewer.entities.add({ |
|
|
|
id: 'sector-' + new Date().getTime(), |
|
|
|
name: `扇形 ${this.entityCounter}`, |
|
|
|
polygon: { |
|
|
|
hierarchy: new Cesium.PolygonHierarchy(positions), |
|
|
|
material: Cesium.Color.fromCssColorString(this.defaultStyles.sector.color).withAlpha(0.5), |
|
|
|
outline: true, |
|
|
|
outlineColor: Cesium.Color.fromCssColorString(this.defaultStyles.sector.color), |
|
|
|
outlineWidth: this.defaultStyles.sector.width, |
|
|
|
perPositionHeight: false |
|
|
|
} |
|
|
|
}); |
|
|
|
|
|
|
|
// 4. 记录实体 |
|
|
|
this.entityCounter++; |
|
|
|
const entityData = { |
|
|
|
id: `sector_${this.entityCounter}`, |
|
|
|
type: 'sector', |
|
|
|
center: this.cartesianToLatLng(centerPoint), |
|
|
|
radius: radius, |
|
|
|
startAngle: startAngle, |
|
|
|
endAngle: endAngle, |
|
|
|
positions: positions, |
|
|
|
entity: finalEntity, |
|
|
|
color: this.defaultStyles.sector.color, |
|
|
|
opacity: 0.5, |
|
|
|
width: this.defaultStyles.sector.width, |
|
|
|
label: `扇形 ${this.entityCounter}` |
|
|
|
}; |
|
|
|
this.allEntities.push(entityData); |
|
|
|
|
|
|
|
// 5. 结束绘制状态 |
|
|
|
this.stopDrawing(); |
|
|
|
}, |
|
|
|
|
|
|
|
// 计算角度 |
|
|
|
calculateAngle(center, start, end) { |
|
|
|
const startLL = Cesium.Cartographic.fromCartesian(start); |
|
|
|
const endLL = Cesium.Cartographic.fromCartesian(end); |
|
|
|
const centerLL = Cesium.Cartographic.fromCartesian(center); |
|
|
|
|
|
|
|
// 计算两点相对于圆心的角度 |
|
|
|
const startAngle = Math.atan2(startLL.longitude - centerLL.longitude, startLL.latitude - centerLL.latitude); |
|
|
|
const endAngle = Math.atan2(endLL.longitude - centerLL.longitude, endLL.latitude - centerLL.latitude); |
|
|
|
|
|
|
|
// 返回角度差 |
|
|
|
return endAngle - startAngle; |
|
|
|
}, |
|
|
|
|
|
|
|
// 计算角度差 |
|
|
|
calculateAngleDiff(center, start, end) { |
|
|
|
const startLL = Cesium.Cartographic.fromCartesian(start); |
|
|
|
const endLL = Cesium.Cartographic.fromCartesian(end); |
|
|
|
const centerLL = Cesium.Cartographic.fromCartesian(center); |
|
|
|
|
|
|
|
// 计算两点相对于圆心的角度 |
|
|
|
const startAngle = Math.atan2(startLL.longitude - centerLL.longitude, startLL.latitude - centerLL.latitude); |
|
|
|
const endAngle = Math.atan2(endLL.longitude - centerLL.longitude, endLL.latitude - centerLL.latitude); |
|
|
|
|
|
|
|
// 计算角度差(确保为正值) |
|
|
|
let angleDiff = endAngle - startAngle; |
|
|
|
if (angleDiff < 0) { |
|
|
|
angleDiff += 2 * Math.PI; |
|
|
|
} |
|
|
|
|
|
|
|
// 确保角度差在合理范围内 |
|
|
|
return Math.max(0.1, Math.min(Math.PI * 2, angleDiff)); |
|
|
|
}, |
|
|
|
|
|
|
|
// 计算点相对于圆心的角度 |
|
|
|
calculatePointAngle(center, point) { |
|
|
|
const pointLL = Cesium.Cartographic.fromCartesian(point); |
|
|
|
const centerLL = Cesium.Cartographic.fromCartesian(center); |
|
|
|
|
|
|
|
// 计算点相对于圆心的角度 |
|
|
|
const angle = Math.atan2(pointLL.longitude - centerLL.longitude, pointLL.latitude - centerLL.latitude); |
|
|
|
return angle; |
|
|
|
}, |
|
|
|
|
|
|
|
// 生成扇形顶点位置 |
|
|
|
generateSectorPositions(center, radius, startAngle, endAngle) { |
|
|
|
const positions = []; |
|
|
|
const centerLL = Cesium.Cartographic.fromCartesian(center); |
|
|
|
|
|
|
|
// 添加圆心 |
|
|
|
positions.push(center); |
|
|
|
|
|
|
|
// 计算角度差 |
|
|
|
let angleDiff = endAngle - startAngle; |
|
|
|
if (angleDiff < 0) { |
|
|
|
angleDiff += 2 * Math.PI; |
|
|
|
} |
|
|
|
|
|
|
|
// 确保角度差不为零 |
|
|
|
angleDiff = Math.max(0.01, angleDiff); |
|
|
|
|
|
|
|
// 计算扇形的顶点数(根据角度差确定,确保平滑) |
|
|
|
const numPoints = Math.max(5, Math.ceil(angleDiff * 180 / Math.PI / 10)); |
|
|
|
const angleStep = angleDiff / (numPoints - 1); |
|
|
|
|
|
|
|
// 生成扇形的顶点 |
|
|
|
for (let i = 0; i < numPoints; i++) { |
|
|
|
const currentAngle = startAngle + i * angleStep; |
|
|
|
const distance = radius / 6378137; // 转换为弧度 |
|
|
|
|
|
|
|
const lat = centerLL.latitude + Math.cos(currentAngle) * distance; |
|
|
|
const lng = centerLL.longitude + Math.sin(currentAngle) * distance / Math.cos(centerLL.latitude); |
|
|
|
|
|
|
|
const position = Cesium.Cartesian3.fromRadians(lng, lat); |
|
|
|
positions.push(position); |
|
|
|
} |
|
|
|
|
|
|
|
// 闭合扇形 |
|
|
|
positions.push(center); |
|
|
|
|
|
|
|
return positions; |
|
|
|
}, |
|
|
|
|
|
|
|
// 计算两点之间的距离(米) |
|
|
|
calculateDistance(point1, point2) { |
|
|
|
return Cesium.Cartesian3.distance(point1, point2); |
|
|
|
}, |
|
|
|
|
|
|
|
// 绘制箭头 |
|
|
|
startArrowDrawing() { |
|
|
|
this.drawingPoints = []; // 存储起点和终点 |
|
|
|
let activeCursorPosition = null; // 实时鼠标位置 |
|
|
|
|
|
|
|
// 1. 清理旧实体 |
|
|
|
if (this.tempEntity) this.viewer.entities.remove(this.tempEntity); |
|
|
|
if (this.tempPreviewEntity) this.viewer.entities.remove(this.tempPreviewEntity); |
|
|
|
this.tempEntity = null; |
|
|
|
this.tempPreviewEntity = null; |
|
|
|
|
|
|
|
// 2. 鼠标移动事件 |
|
|
|
this.drawingHandler.setInputAction((movement) => { |
|
|
|
const newPosition = this.getClickPosition(movement.endPosition); |
|
|
|
if (newPosition) { |
|
|
|
activeCursorPosition = newPosition; |
|
|
|
} |
|
|
|
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE); |
|
|
|
|
|
|
|
// 3. 鼠标点击事件 |
|
|
|
this.drawingHandler.setInputAction((click) => { |
|
|
|
const position = this.getClickPosition(click.position); |
|
|
|
if (position) { |
|
|
|
this.drawingPoints.push(position); |
|
|
|
|
|
|
|
// --- 情况A:第一次点击(确定起点) --- |
|
|
|
if (this.drawingPoints.length === 1) { |
|
|
|
activeCursorPosition = position; // 初始化鼠标位置 |
|
|
|
|
|
|
|
// 创建动态预览箭头 |
|
|
|
this.tempPreviewEntity = this.viewer.entities.add({ |
|
|
|
polyline: { |
|
|
|
// 使用 CallbackProperty 动态获取位置 |
|
|
|
positions: new Cesium.CallbackProperty(() => { |
|
|
|
// 只有当有点且鼠标位置存在时才渲染 |
|
|
|
if (this.drawingPoints.length > 0 && activeCursorPosition) { |
|
|
|
// 获取最后一个已确认的点 |
|
|
|
const lastPoint = this.drawingPoints[this.drawingPoints.length - 1]; |
|
|
|
// 返回 [最后一个点, 当前鼠标位置] |
|
|
|
return [lastPoint, activeCursorPosition]; |
|
|
|
} |
|
|
|
return []; |
|
|
|
}, false), |
|
|
|
width: 8, // 增加宽度以获得更大的箭头头部 |
|
|
|
// 使用箭头材质 |
|
|
|
material: new Cesium.PolylineArrowMaterialProperty( |
|
|
|
Cesium.Color.fromCssColorString(this.defaultStyles.arrow.color) |
|
|
|
), |
|
|
|
clampToGround: true, // 贴地 |
|
|
|
widthInMeters: false // 使用像素宽度模式 |
|
|
|
} |
|
|
|
}); |
|
|
|
} |
|
|
|
// --- 情况B:第二次点击(确定终点) --- |
|
|
|
else { |
|
|
|
// 停止监听鼠标移动,因为形状已确定 |
|
|
|
activeCursorPosition = null; |
|
|
|
|
|
|
|
// 调用完成逻辑 |
|
|
|
this.finishArrowDrawing(); |
|
|
|
} |
|
|
|
} |
|
|
|
}, Cesium.ScreenSpaceEventType.LEFT_CLICK); |
|
|
|
|
|
|
|
// 4. 右键取消 |
|
|
|
this.drawingHandler.setInputAction(() => { |
|
|
|
this.cancelDrawing(); |
|
|
|
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK); |
|
|
|
}, |
|
|
|
|
|
|
|
// 完成箭头绘制 |
|
|
|
finishArrowDrawing() { |
|
|
|
// 将预览箭头转换为最终箭头 |
|
|
|
if (this.drawingPoints.length > 1) { |
|
|
|
// 移除预览箭头 |
|
|
|
if (this.tempPreviewEntity) { |
|
|
|
this.viewer.entities.remove(this.tempPreviewEntity); |
|
|
|
this.tempPreviewEntity = null; |
|
|
|
} |
|
|
|
|
|
|
|
// 创建最终的箭头实体 |
|
|
|
const entity = this.addArrowEntity([...this.drawingPoints]); |
|
|
|
|
|
|
|
this.stopDrawing(); |
|
|
|
return entity; |
|
|
|
} else { |
|
|
|
this.cancelDrawing(); |
|
|
|
return null; |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
// 添加箭头实体 |
|
|
|
addArrowEntity(positions) { |
|
|
|
this.entityCounter++ |
|
|
|
const id = `arrow_${this.entityCounter}` |
|
|
|
|
|
|
|
// 创建箭头实体,使用固定宽度以确保等比例放大 |
|
|
|
const entity = this.viewer.entities.add({ |
|
|
|
id: id, |
|
|
|
name: `箭头 ${this.entityCounter}`, |
|
|
|
polyline: { |
|
|
|
positions: positions, |
|
|
|
width: 8, // 增加宽度以获得更大的箭头头部 |
|
|
|
material: new Cesium.PolylineArrowMaterialProperty( |
|
|
|
Cesium.Color.fromCssColorString(this.defaultStyles.arrow.color) |
|
|
|
), |
|
|
|
clampToGround: true, |
|
|
|
// 使用像素宽度模式,确保箭头在缩放时保持比例 |
|
|
|
widthInMeters: false |
|
|
|
} |
|
|
|
}) |
|
|
|
|
|
|
|
const entityData = { |
|
|
|
id, |
|
|
|
type: 'arrow', |
|
|
|
points: positions.map(p => this.cartesianToLatLng(p)), |
|
|
|
positions: positions, |
|
|
|
entity: entity, |
|
|
|
color: this.defaultStyles.arrow.color, |
|
|
|
width: this.defaultStyles.arrow.width, |
|
|
|
label: `箭头 ${this.entityCounter}` |
|
|
|
} |
|
|
|
|
|
|
|
this.allEntities.push(entityData) |
|
|
|
|
|
|
|
entity.clickHandler = (e) => { |
|
|
|
this.selectEntity(entityData) |
|
|
|
e.stopPropagation() |
|
|
|
} |
|
|
|
|
|
|
|
return entityData |
|
|
|
}, |
|
|
|
|
|
|
|
// 绘制文本框 |
|
|
|
startTextDrawing() { |
|
|
|
// 1. 清理旧实体 |
|
|
|
if (this.tempEntity) this.viewer.entities.remove(this.tempEntity); |
|
|
|
this.tempEntity = null; |
|
|
|
|
|
|
|
// 2. 鼠标点击事件 |
|
|
|
this.drawingHandler.setInputAction((click) => { |
|
|
|
const position = this.getClickPosition(click.position); |
|
|
|
if (position) { |
|
|
|
// 弹出输入框,让用户输入文本内容 |
|
|
|
const text = prompt('请输入文本内容:', '文本'); |
|
|
|
if (text) { |
|
|
|
// 创建文本实体 |
|
|
|
this.addTextEntity(position, text); |
|
|
|
} |
|
|
|
} |
|
|
|
}, Cesium.ScreenSpaceEventType.LEFT_CLICK); |
|
|
|
|
|
|
|
// 3. 右键取消 |
|
|
|
this.drawingHandler.setInputAction(() => { |
|
|
|
this.cancelDrawing(); |
|
|
|
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK); |
|
|
|
}, |
|
|
|
|
|
|
|
// 添加文本实体 |
|
|
|
addTextEntity(position, text) { |
|
|
|
this.entityCounter++ |
|
|
|
const id = `text_${this.entityCounter}` |
|
|
|
|
|
|
|
// 获取经纬度坐标 |
|
|
|
const { lat, lng } = this.cartesianToLatLng(position) |
|
|
|
|
|
|
|
const entity = this.viewer.entities.add({ |
|
|
|
id: id, |
|
|
|
name: `文本 ${this.entityCounter}`, |
|
|
|
position: position, |
|
|
|
label: { |
|
|
|
text: text, |
|
|
|
font: this.defaultStyles.text.font, |
|
|
|
fillColor: Cesium.Color.fromCssColorString(this.defaultStyles.text.color), |
|
|
|
outlineColor: Cesium.Color.BLACK, |
|
|
|
outlineWidth: 2, |
|
|
|
style: Cesium.LabelStyle.FILL_AND_OUTLINE, |
|
|
|
verticalOrigin: Cesium.VerticalOrigin.CENTER, |
|
|
|
horizontalOrigin: Cesium.HorizontalOrigin.CENTER, |
|
|
|
backgroundColor: Cesium.Color.fromCssColorString(this.defaultStyles.text.backgroundColor), |
|
|
|
backgroundPadding: new Cesium.Cartesian2(10, 5), |
|
|
|
pixelOffset: new Cesium.Cartesian2(0, 0), |
|
|
|
// 随地图缩放调整大小 |
|
|
|
scaleByDistance: new Cesium.NearFarScalar( |
|
|
|
1000, // 近距离(米) |
|
|
|
1.0, // 近距离时的缩放比例 |
|
|
|
1000000, // 远距离(米) |
|
|
|
0.0 // 远距离时的缩放比例 |
|
|
|
) |
|
|
|
} |
|
|
|
}) |
|
|
|
|
|
|
|
const entityData = { |
|
|
|
id, |
|
|
|
type: 'text', |
|
|
|
lat, |
|
|
|
lng, |
|
|
|
text: text, |
|
|
|
position: position, |
|
|
|
entity: entity, |
|
|
|
color: this.defaultStyles.text.color, |
|
|
|
font: this.defaultStyles.text.font, |
|
|
|
backgroundColor: this.defaultStyles.text.backgroundColor, |
|
|
|
label: `文本 ${this.entityCounter}` |
|
|
|
} |
|
|
|
|
|
|
|
this.allEntities.push(entityData) |
|
|
|
|
|
|
|
entity.clickHandler = (e) => { |
|
|
|
this.selectEntity(entityData) |
|
|
|
e.stopPropagation() |
|
|
|
} |
|
|
|
|
|
|
|
return entityData |
|
|
|
}, |
|
|
|
|
|
|
|
// 绘制图片 |
|
|
|
startImageDrawing() { |
|
|
|
// 1. 清理旧实体 |
|
|
|
if (this.tempEntity) this.viewer.entities.remove(this.tempEntity); |
|
|
|
this.tempEntity = null; |
|
|
|
|
|
|
|
// 2. 鼠标点击事件 |
|
|
|
this.drawingHandler.setInputAction((click) => { |
|
|
|
const position = this.getClickPosition(click.position); |
|
|
|
if (position) { |
|
|
|
// 创建一个隐藏的文件输入元素 |
|
|
|
const fileInput = document.createElement('input'); |
|
|
|
fileInput.type = 'file'; |
|
|
|
fileInput.accept = 'image/*'; |
|
|
|
|
|
|
|
// 监听文件选择事件 |
|
|
|
fileInput.onchange = (e) => { |
|
|
|
const file = e.target.files[0]; |
|
|
|
if (file) { |
|
|
|
// 创建 FormData 对象用于上传 |
|
|
|
const formData = new FormData(); |
|
|
|
formData.append('file', file); |
|
|
|
|
|
|
|
// 上传文件到服务器 |
|
|
|
this.uploadImage(formData, position); |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
// 触发文件选择对话框 |
|
|
|
fileInput.click(); |
|
|
|
} |
|
|
|
}, Cesium.ScreenSpaceEventType.LEFT_CLICK); |
|
|
|
|
|
|
|
// 3. 右键取消 |
|
|
|
this.drawingHandler.setInputAction(() => { |
|
|
|
this.cancelDrawing(); |
|
|
|
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK); |
|
|
|
}, |
|
|
|
|
|
|
|
// 上传图片 |
|
|
|
uploadImage(formData, position) { |
|
|
|
// 显示加载状态 |
|
|
|
this.$modal.loading('图片上传中,请稍候...'); |
|
|
|
|
|
|
|
try { |
|
|
|
// 创建请求配置 |
|
|
|
const config = { |
|
|
|
headers: { |
|
|
|
'Content-Type': 'multipart/form-data' |
|
|
|
} |
|
|
|
}; |
|
|
|
|
|
|
|
// 发送请求 - 使用项目封装的 request 实例 |
|
|
|
request.post('/common/upload', formData, config) |
|
|
|
.then((response) => { |
|
|
|
this.$modal.closeLoading(); |
|
|
|
|
|
|
|
if (response.code === 200) { |
|
|
|
// 上传成功,获取图片 URL |
|
|
|
let imageUrl = response.url; |
|
|
|
// 创建图片实体 |
|
|
|
this.addImageEntity(position, imageUrl); |
|
|
|
} else { |
|
|
|
this.$modal.msgError('图片上传失败:' + (response.msg || '未知错误')); |
|
|
|
} |
|
|
|
}) |
|
|
|
.catch((error) => { |
|
|
|
this.$modal.closeLoading(); |
|
|
|
|
|
|
|
if (error.response) { |
|
|
|
// 服务器返回错误 |
|
|
|
if (error.response.data && error.response.data.msg) { |
|
|
|
this.$modal.msgError('图片上传失败:' + error.response.data.msg); |
|
|
|
} else { |
|
|
|
this.$modal.msgError('图片上传失败:服务器返回错误 ' + error.response.status); |
|
|
|
} |
|
|
|
} else if (error.request) { |
|
|
|
// 请求发送但没有收到响应 |
|
|
|
this.$modal.msgError('图片上传失败:无法连接到服务器,请检查网络'); |
|
|
|
} else { |
|
|
|
// 请求配置错误 |
|
|
|
this.$modal.msgError('图片上传失败:' + error.message); |
|
|
|
} |
|
|
|
}); |
|
|
|
} catch (error) { |
|
|
|
this.$modal.closeLoading(); |
|
|
|
this.$modal.msgError('图片上传失败:' + error.message); |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
// 添加图片实体 |
|
|
|
addImageEntity(position, imageUrl) { |
|
|
|
this.entityCounter++ |
|
|
|
const id = `image_${this.entityCounter}` |
|
|
|
|
|
|
|
// 获取经纬度坐标 |
|
|
|
const { lat, lng } = this.cartesianToLatLng(position) |
|
|
|
|
|
|
|
const entity = this.viewer.entities.add({ |
|
|
|
id: id, |
|
|
|
name: `图片 ${this.entityCounter}`, |
|
|
|
position: position, |
|
|
|
billboard: { |
|
|
|
image: imageUrl, |
|
|
|
width: this.defaultStyles.image.width, |
|
|
|
height: this.defaultStyles.image.height, |
|
|
|
verticalOrigin: Cesium.VerticalOrigin.CENTER, |
|
|
|
horizontalOrigin: Cesium.HorizontalOrigin.CENTER, |
|
|
|
// 随地图缩放调整大小 |
|
|
|
scaleByDistance: new Cesium.NearFarScalar( |
|
|
|
1000, // 近距离(米) |
|
|
|
1.0, // 近距离时的缩放比例 |
|
|
|
1000000, // 远距离(米) |
|
|
|
0.0 // 远距离时的缩放比例 |
|
|
|
) |
|
|
|
} |
|
|
|
}) |
|
|
|
|
|
|
|
const entityData = { |
|
|
|
id, |
|
|
|
type: 'image', |
|
|
|
lat, |
|
|
|
lng, |
|
|
|
imageUrl: imageUrl, |
|
|
|
position: position, |
|
|
|
entity: entity, |
|
|
|
width: this.defaultStyles.image.width, |
|
|
|
height: this.defaultStyles.image.height, |
|
|
|
label: `图片 ${this.entityCounter}` |
|
|
|
} |
|
|
|
|
|
|
|
this.allEntities.push(entityData) |
|
|
|
|
|
|
|
entity.clickHandler = (e) => { |
|
|
|
this.selectEntity(entityData) |
|
|
|
e.stopPropagation() |
|
|
|
} |
|
|
|
|
|
|
|
return entityData |
|
|
|
}, |
|
|
|
|
|
|
|
// ================== 实体创建方法 ================== |
|
|
|
|
|
|
|
addPointEntity(lat, lng) { |
|
|
|
@ -1060,19 +1675,7 @@ export default { |
|
|
|
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 |
|
|
|
}, |
|
|
|
|
|
|
|
// ================== 实体管理 ================== |
|
|
|
|
|
|
|
@ -1219,7 +1822,11 @@ export default { |
|
|
|
line: '线', |
|
|
|
polygon: '面', |
|
|
|
rectangle: '矩形', |
|
|
|
circle: '圆形' |
|
|
|
circle: '圆形', |
|
|
|
sector: '扇形', |
|
|
|
arrow: '箭头', |
|
|
|
text: '文本', |
|
|
|
image: '图片' |
|
|
|
} |
|
|
|
return names[type] || type |
|
|
|
}, |
|
|
|
|