|
|
|
@ -34,6 +34,9 @@ |
|
|
|
:entity-data="contextMenu.entityData" |
|
|
|
@delete="deleteEntityFromContextMenu" |
|
|
|
@update-property="updateEntityProperty" |
|
|
|
@edit-platform-position="openPlatformIconPositionDialog" |
|
|
|
@edit-platform-heading="openPlatformIconHeadingDialog" |
|
|
|
@show-transform-box="showPlatformIconTransformBox" |
|
|
|
/> |
|
|
|
|
|
|
|
<!-- 定位弹窗 --> |
|
|
|
@ -44,6 +47,11 @@ |
|
|
|
@cancel="handleLocateCancel" |
|
|
|
/> |
|
|
|
|
|
|
|
<!-- 平台图标旋转模式提示 --> |
|
|
|
<div v-if="platformIconRotateTip" class="platform-icon-rotate-tip"> |
|
|
|
{{ platformIconRotateTip }} |
|
|
|
</div> |
|
|
|
|
|
|
|
<!-- 地图右下角:比例尺 + 经纬度 --> |
|
|
|
<div class="map-info-panel"> |
|
|
|
<div class="scale-bar" @click="handleScaleClick"> |
|
|
|
@ -174,7 +182,25 @@ export default { |
|
|
|
currentScaleUnit: 'm', |
|
|
|
isApplyingScale: false, |
|
|
|
// 定位相关 |
|
|
|
locateDialogVisible: false |
|
|
|
locateDialogVisible: false, |
|
|
|
// 平台图标图形化编辑:拖拽移动、旋转、伸缩框 |
|
|
|
draggingPlatformIcon: null, |
|
|
|
rotatingPlatformIcon: null, |
|
|
|
platformIconRotateTip: '', |
|
|
|
platformIconDragCameraEnabled: true, |
|
|
|
selectedPlatformIcon: null, |
|
|
|
pendingDragIcon: null, |
|
|
|
dragStartScreenPos: null, |
|
|
|
draggingRotateHandle: null, |
|
|
|
draggingScaleHandle: null, |
|
|
|
clickedOnEmpty: false, |
|
|
|
PLATFORM_ICON_BASE_SIZE: 72, |
|
|
|
PLATFORM_SCALE_BOX_HALF_DEG: 0.0005, |
|
|
|
PLATFORM_SCALE_BOX_MIN_HALF_DEG: 0.0007, |
|
|
|
PLATFORM_ROTATE_HANDLE_OFFSET_DEG: 0.0009, |
|
|
|
PLATFORM_DRAG_THRESHOLD_PX: 10, |
|
|
|
DESIRED_BOX_HALF_PX: 58, |
|
|
|
DESIRED_ROTATE_OFFSET_PX: 50 |
|
|
|
} |
|
|
|
}, |
|
|
|
components: { |
|
|
|
@ -538,6 +564,234 @@ export default { |
|
|
|
const backendUrl = process.env.VUE_APP_BACKEND_URL || ''; |
|
|
|
return backendUrl + cleanPath; |
|
|
|
}, |
|
|
|
|
|
|
|
/** 默认平台图标(无 imageUrl 时使用):简单飞机剪影 SVG */ |
|
|
|
getDefaultPlatformIconDataUrl() { |
|
|
|
const svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32"><path fill="%23666" d="M28 14L18 8l-4 2v4L8 10 4 14l4 2 2 4-4 2v4l4 2 4-2 2 4 2-2v-6l8-4 4-2v-4l-4 2-8-4z"/></svg>'; |
|
|
|
return 'data:image/svg+xml,' + encodeURIComponent(svg); |
|
|
|
}, |
|
|
|
|
|
|
|
/** 伸缩框旋转手柄图标 SVG:蓝底、白边、白色弧形箭头 */ |
|
|
|
getRotationHandleIconDataUrl() { |
|
|
|
const svg = '<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">' + |
|
|
|
'<circle cx="16" cy="16" r="14" fill="#008aff" stroke="#fff" stroke-width="2"/>' + |
|
|
|
'<path fill="none" stroke="#fff" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" ' + |
|
|
|
'd="M9 16 A7 7 0 0 1 23 16"/>' + |
|
|
|
'<path fill="#fff" stroke="#fff" stroke-width="1" d="M22 14.2 L25 16 L22 17.8 Z"/>' + |
|
|
|
'</svg>'; |
|
|
|
return 'data:image/svg+xml,' + encodeURIComponent(svg); |
|
|
|
}, |
|
|
|
|
|
|
|
/** 从手柄实体 id 解析出平台图标 entityData 与类型(rotate / scale-0~3) */ |
|
|
|
getPlatformIconDataFromHandleId(handleEntityId) { |
|
|
|
if (!handleEntityId || typeof handleEntityId !== 'string') return null; |
|
|
|
if (handleEntityId.endsWith('-rotate-handle')) { |
|
|
|
const baseId = handleEntityId.replace(/-rotate-handle$/, ''); |
|
|
|
const ed = this.allEntities.find(e => e.type === 'platformIcon' && e.id === baseId); |
|
|
|
return ed ? { entityData: ed, type: 'rotate' } : null; |
|
|
|
} |
|
|
|
const scaleIdx = handleEntityId.lastIndexOf('-scale-'); |
|
|
|
if (scaleIdx !== -1) { |
|
|
|
const baseId = handleEntityId.substring(0, scaleIdx); |
|
|
|
const cornerIndex = parseInt(handleEntityId.substring(scaleIdx + 7), 10); |
|
|
|
if (!isNaN(cornerIndex) && cornerIndex >= 0 && cornerIndex <= 3) { |
|
|
|
const ed = this.allEntities.find(e => e.type === 'platformIcon' && e.id === baseId); |
|
|
|
return ed ? { entityData: ed, type: 'scale', cornerIndex } : null; |
|
|
|
} |
|
|
|
} |
|
|
|
return null; |
|
|
|
}, |
|
|
|
|
|
|
|
/** 在当前视野下,图标位置处 1 像素对应的经纬度(用于伸缩框固定屏幕尺寸) */ |
|
|
|
getDegreesPerPixelAt(lng, lat) { |
|
|
|
if (!this.viewer || this.viewer.scene.mode !== Cesium.SceneMode.SCENE2D) { |
|
|
|
return { degPerPxLng: this.PLATFORM_SCALE_BOX_HALF_DEG / this.DESIRED_BOX_HALF_PX, degPerPxLat: this.PLATFORM_SCALE_BOX_HALF_DEG / this.DESIRED_BOX_HALF_PX }; |
|
|
|
} |
|
|
|
const center = Cesium.Cartesian3.fromDegrees(lng, lat); |
|
|
|
const east = Cesium.Cartesian3.fromDegrees(lng + 0.005, lat); |
|
|
|
const north = Cesium.Cartesian3.fromDegrees(lng, lat + 0.005); |
|
|
|
const sc = Cesium.SceneTransforms.worldToWindowCoordinates(this.viewer.scene, center); |
|
|
|
const se = Cesium.SceneTransforms.worldToWindowCoordinates(this.viewer.scene, east); |
|
|
|
const sn = Cesium.SceneTransforms.worldToWindowCoordinates(this.viewer.scene, north); |
|
|
|
if (!sc || !se || !sn) { |
|
|
|
return { degPerPxLng: this.PLATFORM_SCALE_BOX_HALF_DEG / this.DESIRED_BOX_HALF_PX, degPerPxLat: this.PLATFORM_SCALE_BOX_HALF_DEG / this.DESIRED_BOX_HALF_PX }; |
|
|
|
} |
|
|
|
const pxLng = Math.max(1, Math.abs(se.x - sc.x)); |
|
|
|
const pxLat = Math.max(1, Math.abs(sn.y - sc.y)); |
|
|
|
return { |
|
|
|
degPerPxLng: 0.005 / pxLng, |
|
|
|
degPerPxLat: 0.005 / pxLat |
|
|
|
}; |
|
|
|
}, |
|
|
|
|
|
|
|
/** 更新平台图标 billboard 的宽高(根据 iconScale) */ |
|
|
|
updatePlatformIconBillboardSize(entityData) { |
|
|
|
if (!entityData || !entityData.entity || !entityData.entity.billboard) return; |
|
|
|
const scale = Math.max(0.2, Math.min(3, entityData.iconScale || 1)); |
|
|
|
entityData.iconScale = scale; |
|
|
|
const size = this.PLATFORM_ICON_BASE_SIZE * scale; |
|
|
|
entityData.entity.billboard.width = new Cesium.ConstantProperty(size); |
|
|
|
entityData.entity.billboard.height = new Cesium.ConstantProperty(size); |
|
|
|
}, |
|
|
|
|
|
|
|
/** 显示伸缩框:旋转手柄 + 四角缩放手柄 + 矩形边线(按屏幕像素固定尺寸,任意缩放都易点) */ |
|
|
|
showTransformHandles(entityData) { |
|
|
|
if (!this.viewer || !entityData || entityData.type !== 'platformIcon') return; |
|
|
|
this.removeTransformHandles(entityData); |
|
|
|
const id = entityData.id; |
|
|
|
const lng = entityData.lng; |
|
|
|
const lat = entityData.lat; |
|
|
|
const dpp = this.getDegreesPerPixelAt(lng, lat); |
|
|
|
const baseHalfDeg = this.DESIRED_BOX_HALF_PX * Math.min(dpp.degPerPxLng, dpp.degPerPxLat); |
|
|
|
const half = Math.max((entityData.iconScale || 1) * baseHalfDeg, baseHalfDeg * 0.5); |
|
|
|
const rotOffsetLat = this.DESIRED_ROTATE_OFFSET_PX * dpp.degPerPxLat; |
|
|
|
const rotOffset = Math.max(rotOffsetLat, this.PLATFORM_ROTATE_HANDLE_OFFSET_DEG); |
|
|
|
const rotationHandle = this.viewer.entities.add({ |
|
|
|
id: id + '-rotate-handle', |
|
|
|
position: Cesium.Cartesian3.fromDegrees(lng, lat + rotOffset), |
|
|
|
billboard: { |
|
|
|
image: this.getRotationHandleIconDataUrl(), |
|
|
|
width: 36, |
|
|
|
height: 36, |
|
|
|
verticalOrigin: Cesium.VerticalOrigin.BOTTOM, |
|
|
|
horizontalOrigin: Cesium.HorizontalOrigin.CENTER, |
|
|
|
disableDepthTestDistance: Number.POSITIVE_INFINITY |
|
|
|
} |
|
|
|
}); |
|
|
|
const corners = [ |
|
|
|
Cesium.Cartesian3.fromDegrees(lng + half, lat + half), |
|
|
|
Cesium.Cartesian3.fromDegrees(lng - half, lat + half), |
|
|
|
Cesium.Cartesian3.fromDegrees(lng - half, lat - half), |
|
|
|
Cesium.Cartesian3.fromDegrees(lng + half, lat - half) |
|
|
|
]; |
|
|
|
const scaleHandles = corners.map((pos, i) => |
|
|
|
this.viewer.entities.add({ |
|
|
|
id: id + '-scale-' + i, |
|
|
|
position: pos, |
|
|
|
point: { |
|
|
|
pixelSize: 14, |
|
|
|
color: Cesium.Color.WHITE, |
|
|
|
outlineColor: Cesium.Color.fromCssColorString('#008aff'), |
|
|
|
outlineWidth: 3, |
|
|
|
disableDepthTestDistance: Number.POSITIVE_INFINITY |
|
|
|
} |
|
|
|
}) |
|
|
|
); |
|
|
|
const linePositions = [corners[0], corners[1], corners[2], corners[3], corners[0]]; |
|
|
|
const frameLine = this.viewer.entities.add({ |
|
|
|
id: id + '-scale-frame', |
|
|
|
polyline: { |
|
|
|
positions: linePositions, |
|
|
|
width: 3, |
|
|
|
material: Cesium.Color.fromCssColorString('#008aff'), |
|
|
|
clampToGround: true, |
|
|
|
disableDepthTestDistance: Number.POSITIVE_INFINITY |
|
|
|
} |
|
|
|
}); |
|
|
|
entityData.transformHandles = { |
|
|
|
rotation: rotationHandle, |
|
|
|
scale: scaleHandles, |
|
|
|
frame: frameLine |
|
|
|
}; |
|
|
|
}, |
|
|
|
|
|
|
|
/** 移除伸缩框手柄与边线 */ |
|
|
|
removeTransformHandles(entityData) { |
|
|
|
if (!entityData || !entityData.transformHandles) return; |
|
|
|
const h = entityData.transformHandles; |
|
|
|
if (h.rotation) this.viewer.entities.remove(h.rotation); |
|
|
|
if (h.scale) h.scale.forEach(e => this.viewer.entities.remove(e)); |
|
|
|
if (h.frame) this.viewer.entities.remove(h.frame); |
|
|
|
entityData.transformHandles = null; |
|
|
|
}, |
|
|
|
|
|
|
|
/** 根据图标当前位置、iconScale 与当前视野更新伸缩框(保持固定像素尺寸) */ |
|
|
|
updateTransformHandlePositions(entityData) { |
|
|
|
if (!entityData || !entityData.transformHandles) return; |
|
|
|
const lng = entityData.lng; |
|
|
|
const lat = entityData.lat; |
|
|
|
const dpp = this.getDegreesPerPixelAt(lng, lat); |
|
|
|
const baseHalfDeg = this.DESIRED_BOX_HALF_PX * Math.min(dpp.degPerPxLng, dpp.degPerPxLat); |
|
|
|
const half = Math.max((entityData.iconScale || 1) * baseHalfDeg, baseHalfDeg * 0.5); |
|
|
|
const rotOffsetLat = this.DESIRED_ROTATE_OFFSET_PX * dpp.degPerPxLat; |
|
|
|
const rotOffset = Math.max(rotOffsetLat, this.PLATFORM_ROTATE_HANDLE_OFFSET_DEG); |
|
|
|
entityData.transformHandles.rotation.position = Cesium.Cartesian3.fromDegrees(lng, lat + rotOffset); |
|
|
|
const corners = [ |
|
|
|
Cesium.Cartesian3.fromDegrees(lng + half, lat + half), |
|
|
|
Cesium.Cartesian3.fromDegrees(lng - half, lat + half), |
|
|
|
Cesium.Cartesian3.fromDegrees(lng - half, lat - half), |
|
|
|
Cesium.Cartesian3.fromDegrees(lng + half, lat - half) |
|
|
|
]; |
|
|
|
entityData.transformHandles.scale.forEach((ent, i) => { ent.position = corners[i]; }); |
|
|
|
const linePositions = [corners[0], corners[1], corners[2], corners[3], corners[0]]; |
|
|
|
entityData.transformHandles.frame.polyline.positions = new Cesium.ConstantProperty(linePositions); |
|
|
|
this.viewer.scene.requestRender(); |
|
|
|
}, |
|
|
|
|
|
|
|
/** |
|
|
|
* 从右侧平台列表拖拽放置到地图:在放置点添加平台图标实体,支持后续修改位置与朝向。 |
|
|
|
* @param {Object} platform - 平台数据 { id, name, type, imageUrl, iconUrl, icon, color } |
|
|
|
* @param {number} clientX - 放置点的视口 X |
|
|
|
* @param {number} clientY - 放置点的视口 Y |
|
|
|
*/ |
|
|
|
addPlatformIconFromDrag(platform, clientX, clientY) { |
|
|
|
if (!this.viewer || !platform) return; |
|
|
|
const canvas = this.viewer.scene.canvas; |
|
|
|
const rect = canvas.getBoundingClientRect(); |
|
|
|
const x = clientX - rect.left; |
|
|
|
const y = clientY - rect.top; |
|
|
|
const cartesian = this.viewer.camera.pickEllipsoid(new Cesium.Cartesian2(x, y), this.viewer.scene.globe.ellipsoid); |
|
|
|
if (!cartesian) { |
|
|
|
this.$message && this.$message.warning('请将图标放置到地图有效区域内'); |
|
|
|
return; |
|
|
|
} |
|
|
|
const iconUrl = platform.imageUrl || platform.iconUrl; |
|
|
|
const imageSrc = iconUrl ? this.formatPlatformIconUrl(iconUrl) : this.getDefaultPlatformIconDataUrl(); |
|
|
|
this.entityCounter++; |
|
|
|
const id = `platformIcon_${this.entityCounter}`; |
|
|
|
const headingDeg = 0; |
|
|
|
const rotation = Math.PI / 2 - (headingDeg * Math.PI / 180); |
|
|
|
const iconScale = 1.0; |
|
|
|
const size = this.PLATFORM_ICON_BASE_SIZE * iconScale; |
|
|
|
const entity = this.viewer.entities.add({ |
|
|
|
id, |
|
|
|
name: platform.name || '平台', |
|
|
|
position: cartesian, |
|
|
|
billboard: { |
|
|
|
image: imageSrc, |
|
|
|
width: size, |
|
|
|
height: size, |
|
|
|
verticalOrigin: Cesium.VerticalOrigin.CENTER, |
|
|
|
horizontalOrigin: Cesium.HorizontalOrigin.CENTER, |
|
|
|
rotation, |
|
|
|
scaleByDistance: new Cesium.NearFarScalar(500, 1.2, 200000, 0.35), |
|
|
|
translucencyByDistance: new Cesium.NearFarScalar(1000, 1.0, 500000, 0.6) |
|
|
|
} |
|
|
|
}); |
|
|
|
const { lat, lng } = this.cartesianToLatLng(cartesian); |
|
|
|
const entityData = { |
|
|
|
id, |
|
|
|
type: 'platformIcon', |
|
|
|
platformId: platform.id, |
|
|
|
platform, |
|
|
|
name: platform.name || '平台', |
|
|
|
heading: headingDeg, |
|
|
|
lat, |
|
|
|
lng, |
|
|
|
entity, |
|
|
|
imageUrl: iconUrl, |
|
|
|
label: platform.name || '平台', |
|
|
|
iconScale, |
|
|
|
transformHandles: null |
|
|
|
}; |
|
|
|
this.allEntities.push(entityData); |
|
|
|
this.$nextTick(() => { |
|
|
|
if (this.selectedPlatformIcon) this.removeTransformHandles(this.selectedPlatformIcon); |
|
|
|
this.selectedPlatformIcon = entityData; |
|
|
|
this.showTransformHandles(entityData); |
|
|
|
this.$message && this.$message.success('已放置。上方箭头旋转、四角调大小、拖动图标移动;点击空白收起'); |
|
|
|
}); |
|
|
|
}, |
|
|
|
//正式航线渲染函数 |
|
|
|
renderRouteWaypoints(waypoints, routeId = 'default', platformId, platform, style) { |
|
|
|
if (!waypoints || waypoints.length < 1) return; |
|
|
|
@ -1238,6 +1492,7 @@ export default { |
|
|
|
}) |
|
|
|
this.initScaleBar() |
|
|
|
this.initPointMovement() |
|
|
|
this.initPlatformIconInteraction() |
|
|
|
this.initRightClickHandler() |
|
|
|
this.initHoverHandler() |
|
|
|
this.initMouseCoordinates() |
|
|
|
@ -1254,6 +1509,10 @@ export default { |
|
|
|
const pickedObject = this.viewer.scene.pick(click.position); |
|
|
|
if (Cesium.defined(pickedObject) && pickedObject.id) { |
|
|
|
const entity = pickedObject.id; |
|
|
|
const idStr = (entity && entity.id) ? entity.id : ''; |
|
|
|
if (idStr && (idStr.endsWith('-rotate-handle') || idStr.indexOf('-scale-') !== -1)) return; |
|
|
|
const platformIconData = this.allEntities.find(e => e.type === 'platformIcon' && e.entity === entity); |
|
|
|
if (platformIconData) return; |
|
|
|
|
|
|
|
// --- 修正后的安全日志 --- |
|
|
|
console.log(">>> [点击检测] 实体ID:", entity.id); |
|
|
|
@ -1328,6 +1587,183 @@ export default { |
|
|
|
} |
|
|
|
}, Cesium.ScreenSpaceEventType.RIGHT_CLICK) |
|
|
|
}, |
|
|
|
|
|
|
|
/** 平台图标图形化操作:伸缩框(旋转手柄 + 四角缩放)、拖拽移动、单击选中 */ |
|
|
|
initPlatformIconInteraction() { |
|
|
|
const canvas = this.viewer.scene.canvas; |
|
|
|
this.platformIconHandler = new Cesium.ScreenSpaceEventHandler(canvas); |
|
|
|
|
|
|
|
this.platformIconHandler.setInputAction((click) => { |
|
|
|
if (this.isDrawing || this.rotatingPlatformIcon) return; |
|
|
|
const picked = this.viewer.scene.pick(click.position); |
|
|
|
this.clickedOnEmpty = !Cesium.defined(picked) || !picked.id; |
|
|
|
if (picked && picked.id) { |
|
|
|
const idStr = typeof picked.id === 'string' ? picked.id : (picked.id.id || ''); |
|
|
|
if (idStr.endsWith('-scale-frame')) { |
|
|
|
this.clickedOnEmpty = false; |
|
|
|
return; |
|
|
|
} |
|
|
|
const handleInfo = this.getPlatformIconDataFromHandleId(idStr); |
|
|
|
if (handleInfo) { |
|
|
|
this.clickedOnEmpty = false; |
|
|
|
if (handleInfo.type === 'rotate') { |
|
|
|
this.draggingRotateHandle = handleInfo.entityData; |
|
|
|
this.platformIconDragCameraEnabled = this.viewer.scene.screenSpaceCameraController.enableInputs; |
|
|
|
this.viewer.scene.screenSpaceCameraController.enableInputs = false; |
|
|
|
return; |
|
|
|
} |
|
|
|
if (handleInfo.type === 'scale') { |
|
|
|
this.draggingScaleHandle = { entityData: handleInfo.entityData, cornerIndex: handleInfo.cornerIndex }; |
|
|
|
this.platformIconDragCameraEnabled = this.viewer.scene.screenSpaceCameraController.enableInputs; |
|
|
|
this.viewer.scene.screenSpaceCameraController.enableInputs = false; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
const entityData = this.allEntities.find(e => e.type === 'platformIcon' && e.entity === picked.id); |
|
|
|
if (entityData) { |
|
|
|
this.pendingDragIcon = entityData; |
|
|
|
this.dragStartScreenPos = { x: click.position.x, y: click.position.y }; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
}, Cesium.ScreenSpaceEventType.LEFT_DOWN); |
|
|
|
|
|
|
|
this.platformIconHandler.setInputAction((movement) => { |
|
|
|
if (this.draggingRotateHandle) { |
|
|
|
const ed = this.draggingRotateHandle; |
|
|
|
if (ed.entity && ed.entity.position) { |
|
|
|
const now = Cesium.JulianDate.now(); |
|
|
|
const position = ed.entity.position.getValue(now); |
|
|
|
if (position) { |
|
|
|
const screenPos = Cesium.SceneTransforms.worldToWindowCoordinates(this.viewer.scene, position); |
|
|
|
if (screenPos) { |
|
|
|
const dx = movement.endPosition.x - screenPos.x; |
|
|
|
const dy = movement.endPosition.y - screenPos.y; |
|
|
|
const screenAngle = Math.atan2(dy, dx); |
|
|
|
ed.entity.billboard.rotation = -screenAngle; |
|
|
|
let headingDeg = (screenAngle + Math.PI / 2) * (180 / Math.PI); |
|
|
|
if (headingDeg < 0) headingDeg += 360; |
|
|
|
if (headingDeg >= 360) headingDeg -= 360; |
|
|
|
ed.heading = Math.round(headingDeg); |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
this.viewer.scene.requestRender(); |
|
|
|
return; |
|
|
|
} |
|
|
|
if (this.draggingScaleHandle) { |
|
|
|
const { entityData: ed, cornerIndex } = this.draggingScaleHandle; |
|
|
|
const cartesian = this.viewer.camera.pickEllipsoid(movement.endPosition, this.viewer.scene.globe.ellipsoid); |
|
|
|
if (cartesian) { |
|
|
|
const { lat: newLat, lng: newLng } = this.cartesianToLatLng(cartesian); |
|
|
|
const lng = ed.lng; |
|
|
|
const lat = ed.lat; |
|
|
|
const dpp = this.getDegreesPerPixelAt(lng, lat); |
|
|
|
const baseHalf = this.DESIRED_BOX_HALF_PX * Math.min(dpp.degPerPxLng, dpp.degPerPxLat); |
|
|
|
let newHalfDeg; |
|
|
|
if (cornerIndex === 0) newHalfDeg = Math.min(newLng - lng, newLat - lat); |
|
|
|
else if (cornerIndex === 1) newHalfDeg = Math.min(lng - newLng, newLat - lat); |
|
|
|
else if (cornerIndex === 2) newHalfDeg = Math.min(lng - newLng, lat - newLat); |
|
|
|
else newHalfDeg = Math.min(newLng - lng, lat - newLat); |
|
|
|
if (newHalfDeg > 0.0001 && baseHalf > 1e-10) { |
|
|
|
ed.iconScale = Math.max(0.2, Math.min(3, newHalfDeg / baseHalf)); |
|
|
|
this.updatePlatformIconBillboardSize(ed); |
|
|
|
this.updateTransformHandlePositions(ed); |
|
|
|
} |
|
|
|
} |
|
|
|
return; |
|
|
|
} |
|
|
|
if (this.pendingDragIcon) { |
|
|
|
const dx = movement.endPosition.x - this.dragStartScreenPos.x; |
|
|
|
const dy = movement.endPosition.y - this.dragStartScreenPos.y; |
|
|
|
if (Math.sqrt(dx * dx + dy * dy) > (this.PLATFORM_DRAG_THRESHOLD_PX || 10)) { |
|
|
|
this.draggingPlatformIcon = this.pendingDragIcon; |
|
|
|
this.pendingDragIcon = null; |
|
|
|
this.platformIconDragCameraEnabled = this.viewer.scene.screenSpaceCameraController.enableInputs; |
|
|
|
this.viewer.scene.screenSpaceCameraController.enableInputs = false; |
|
|
|
} |
|
|
|
} |
|
|
|
if (this.draggingPlatformIcon) { |
|
|
|
const cartesian = this.viewer.camera.pickEllipsoid(movement.endPosition, this.viewer.scene.globe.ellipsoid); |
|
|
|
if (cartesian) { |
|
|
|
this.draggingPlatformIcon.entity.position = cartesian; |
|
|
|
const { lat, lng } = this.cartesianToLatLng(cartesian); |
|
|
|
this.draggingPlatformIcon.lat = lat; |
|
|
|
this.draggingPlatformIcon.lng = lng; |
|
|
|
if (this.selectedPlatformIcon === this.draggingPlatformIcon) { |
|
|
|
this.updateTransformHandlePositions(this.draggingPlatformIcon); |
|
|
|
} |
|
|
|
} |
|
|
|
this.viewer.scene.requestRender(); |
|
|
|
} |
|
|
|
if (this.rotatingPlatformIcon && this.rotatingPlatformIcon.entity && this.rotatingPlatformIcon.entity.position) { |
|
|
|
const now = Cesium.JulianDate.now(); |
|
|
|
const position = this.rotatingPlatformIcon.entity.position.getValue(now); |
|
|
|
if (position) { |
|
|
|
const screenPos = Cesium.SceneTransforms.worldToWindowCoordinates(this.viewer.scene, position); |
|
|
|
if (screenPos) { |
|
|
|
const dx = movement.endPosition.x - screenPos.x; |
|
|
|
const dy = movement.endPosition.y - screenPos.y; |
|
|
|
const screenAngle = Math.atan2(dy, dx); |
|
|
|
this.rotatingPlatformIcon.entity.billboard.rotation = -screenAngle; |
|
|
|
let headingDeg = (screenAngle + Math.PI / 2) * (180 / Math.PI); |
|
|
|
if (headingDeg < 0) headingDeg += 360; |
|
|
|
if (headingDeg >= 360) headingDeg -= 360; |
|
|
|
this.rotatingPlatformIcon.heading = Math.round(headingDeg); |
|
|
|
} |
|
|
|
} |
|
|
|
this.viewer.scene.requestRender(); |
|
|
|
} |
|
|
|
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE); |
|
|
|
|
|
|
|
this.platformIconHandler.setInputAction(() => { |
|
|
|
if (this.pendingDragIcon) { |
|
|
|
if (this.selectedPlatformIcon === this.pendingDragIcon) { |
|
|
|
this.removeTransformHandles(this.selectedPlatformIcon); |
|
|
|
this.selectedPlatformIcon = null; |
|
|
|
} else { |
|
|
|
if (this.selectedPlatformIcon) this.removeTransformHandles(this.selectedPlatformIcon); |
|
|
|
this.selectedPlatformIcon = this.pendingDragIcon; |
|
|
|
this.showTransformHandles(this.pendingDragIcon); |
|
|
|
} |
|
|
|
this.pendingDragIcon = null; |
|
|
|
this.dragStartScreenPos = null; |
|
|
|
} |
|
|
|
if (this.clickedOnEmpty && this.selectedPlatformIcon) { |
|
|
|
this.removeTransformHandles(this.selectedPlatformIcon); |
|
|
|
this.selectedPlatformIcon = null; |
|
|
|
} |
|
|
|
this.clickedOnEmpty = false; |
|
|
|
if (this.draggingRotateHandle) { |
|
|
|
this.viewer.scene.screenSpaceCameraController.enableInputs = this.platformIconDragCameraEnabled; |
|
|
|
this.draggingRotateHandle = null; |
|
|
|
} |
|
|
|
if (this.draggingScaleHandle) { |
|
|
|
this.viewer.scene.screenSpaceCameraController.enableInputs = this.platformIconDragCameraEnabled; |
|
|
|
this.draggingScaleHandle = null; |
|
|
|
} |
|
|
|
if (this.draggingPlatformIcon) { |
|
|
|
this.viewer.scene.screenSpaceCameraController.enableInputs = this.platformIconDragCameraEnabled; |
|
|
|
this.draggingPlatformIcon = null; |
|
|
|
} |
|
|
|
if (this.rotatingPlatformIcon) { |
|
|
|
this.viewer.scene.screenSpaceCameraController.enableInputs = this.platformIconDragCameraEnabled; |
|
|
|
this.rotatingPlatformIcon = null; |
|
|
|
this.platformIconRotateTip = ''; |
|
|
|
} |
|
|
|
}, Cesium.ScreenSpaceEventType.LEFT_UP); |
|
|
|
|
|
|
|
const onCameraMoveEnd = () => { |
|
|
|
if (this.selectedPlatformIcon && this.selectedPlatformIcon.transformHandles) { |
|
|
|
this.updateTransformHandlePositions(this.selectedPlatformIcon); |
|
|
|
} |
|
|
|
}; |
|
|
|
this.viewer.camera.moveEnd.addEventListener(onCameraMoveEnd); |
|
|
|
this.platformIconCameraListener = () => { |
|
|
|
this.viewer.camera.moveEnd.removeEventListener(onCameraMoveEnd); |
|
|
|
}; |
|
|
|
}, |
|
|
|
|
|
|
|
// 初始化鼠标悬停事件处理器 |
|
|
|
initHoverHandler() { |
|
|
|
// 创建屏幕空间事件处理器 |
|
|
|
@ -3385,6 +3821,53 @@ export default { |
|
|
|
this.contextMenu.visible = false |
|
|
|
} |
|
|
|
}, |
|
|
|
|
|
|
|
/** 右键「显示伸缩框」:选中该图标并显示旋转/缩放手柄 */ |
|
|
|
showPlatformIconTransformBox() { |
|
|
|
const fromMenu = this.contextMenu.entityData |
|
|
|
if (!fromMenu || fromMenu.type !== 'platformIcon' || !fromMenu.entity) { |
|
|
|
this.contextMenu.visible = false |
|
|
|
return |
|
|
|
} |
|
|
|
const ed = this.allEntities.find(e => e.type === 'platformIcon' && e.id === fromMenu.id) || fromMenu |
|
|
|
if (!ed.entity) { |
|
|
|
this.contextMenu.visible = false |
|
|
|
return |
|
|
|
} |
|
|
|
if (ed.lat == null || ed.lng == null) { |
|
|
|
const now = Cesium.JulianDate.now() |
|
|
|
const pos = ed.entity.position && ed.entity.position.getValue(now) |
|
|
|
if (pos) { |
|
|
|
const ll = this.cartesianToLatLng(pos) |
|
|
|
ed.lat = ll.lat |
|
|
|
ed.lng = ll.lng |
|
|
|
} |
|
|
|
} |
|
|
|
if (this.selectedPlatformIcon) this.removeTransformHandles(this.selectedPlatformIcon) |
|
|
|
this.selectedPlatformIcon = ed |
|
|
|
this.showTransformHandles(ed) |
|
|
|
this.contextMenu.visible = false |
|
|
|
this.viewer.scene.requestRender() |
|
|
|
this.$message && this.$message.success('已显示伸缩框') |
|
|
|
}, |
|
|
|
|
|
|
|
/** 位置改为图形化:直接拖动图标即可,无需弹窗 */ |
|
|
|
openPlatformIconPositionDialog() { |
|
|
|
this.contextMenu.visible = false |
|
|
|
this.$message && this.$message.info('请直接拖动图标以修改位置') |
|
|
|
}, |
|
|
|
|
|
|
|
/** 进入旋转模式:移动鼠标设置朝向,单击结束(期间锁定地图) */ |
|
|
|
openPlatformIconHeadingDialog() { |
|
|
|
const ed = this.contextMenu.entityData |
|
|
|
if (!ed || ed.type !== 'platformIcon' || !ed.entity) return |
|
|
|
this.rotatingPlatformIcon = ed |
|
|
|
this.platformIconRotateTip = '移动鼠标设置朝向,单击结束' |
|
|
|
this.platformIconDragCameraEnabled = this.viewer.scene.screenSpaceCameraController.enableInputs |
|
|
|
this.viewer.scene.screenSpaceCameraController.enableInputs = false |
|
|
|
this.contextMenu.visible = false |
|
|
|
this.$message && this.$message.info('移动鼠标可调整朝向,单击地图任意处结束') |
|
|
|
}, |
|
|
|
removeEntity(id) { |
|
|
|
// 查找对应的实体数据 |
|
|
|
const index = this.allEntities.findIndex(e => |
|
|
|
@ -3394,23 +3877,23 @@ export default { |
|
|
|
) |
|
|
|
if (index > -1) { |
|
|
|
const entity = this.allEntities[index] |
|
|
|
// 平台图标:移除伸缩框并清除选中 |
|
|
|
if (entity.type === 'platformIcon') { |
|
|
|
this.removeTransformHandles(entity) |
|
|
|
if (this.selectedPlatformIcon === entity) this.selectedPlatformIcon = null |
|
|
|
} |
|
|
|
// 从地图中移除 |
|
|
|
if (entity instanceof Cesium.Entity) { |
|
|
|
// 情况 A: 直接是 Cesium Entity 对象 |
|
|
|
this.viewer.entities.remove(entity) |
|
|
|
} else if (entity.entity) { |
|
|
|
// 情况 B: 包装对象,包含 entity 属性 |
|
|
|
this.viewer.entities.remove(entity.entity) |
|
|
|
} |
|
|
|
// 移除线实体相关的点实体 |
|
|
|
if (entity.type === 'line' && entity.pointEntities) { |
|
|
|
entity.pointEntities.forEach(pointEntity => { |
|
|
|
this.viewer.entities.remove(pointEntity) |
|
|
|
}) |
|
|
|
} |
|
|
|
// 从数组中移除 |
|
|
|
this.allEntities.splice(index, 1) |
|
|
|
// 如果删除的是选中的实体,清空选中状态 |
|
|
|
if (this.selectedEntity && (this.selectedEntity.id === id || (this.selectedEntity.entity && this.selectedEntity.entity.id === id))) { |
|
|
|
this.selectedEntity = null |
|
|
|
} |
|
|
|
@ -3466,12 +3949,15 @@ export default { |
|
|
|
this.viewer.entities.remove(entity); |
|
|
|
} |
|
|
|
|
|
|
|
// 移除线实体相关的点实体 |
|
|
|
if (item.type === 'line' && item.pointEntities) { |
|
|
|
item.pointEntities.forEach(pointEntity => { |
|
|
|
this.viewer.entities.remove(pointEntity); |
|
|
|
}); |
|
|
|
} |
|
|
|
if (item.type === 'platformIcon') { |
|
|
|
this.removeTransformHandles(item); |
|
|
|
if (this.selectedPlatformIcon === item) this.selectedPlatformIcon = null; |
|
|
|
} |
|
|
|
} catch (e) { |
|
|
|
console.warn('删除实体失败:', e); |
|
|
|
} |
|
|
|
@ -4093,6 +4579,15 @@ export default { |
|
|
|
this.pointMovementHandler = null |
|
|
|
} |
|
|
|
|
|
|
|
if (this.platformIconHandler) { |
|
|
|
this.platformIconHandler.destroy() |
|
|
|
this.platformIconHandler = null |
|
|
|
} |
|
|
|
if (typeof this.platformIconCameraListener === 'function') { |
|
|
|
this.platformIconCameraListener() |
|
|
|
this.platformIconCameraListener = null |
|
|
|
} |
|
|
|
|
|
|
|
if (this.rightClickHandler) { |
|
|
|
this.rightClickHandler.destroy() |
|
|
|
this.rightClickHandler = null |
|
|
|
@ -4209,6 +4704,23 @@ export default { |
|
|
|
display: none !important; |
|
|
|
} |
|
|
|
|
|
|
|
/* 平台图标旋转模式提示条:放在顶部菜单下方,避免被遮挡(顶部栏约 60px) */ |
|
|
|
.platform-icon-rotate-tip { |
|
|
|
position: absolute; |
|
|
|
top: 72px; |
|
|
|
left: 50%; |
|
|
|
transform: translateX(-50%); |
|
|
|
z-index: 99; |
|
|
|
background: rgba(0, 138, 255, 0.95); |
|
|
|
color: #fff; |
|
|
|
padding: 10px 20px; |
|
|
|
border-radius: 6px; |
|
|
|
font-size: 14px; |
|
|
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.25); |
|
|
|
pointer-events: none; |
|
|
|
white-space: nowrap; |
|
|
|
} |
|
|
|
|
|
|
|
/* 地图右下角信息面板:比例尺在上、经纬度在下,整体略下移减少遮挡 */ |
|
|
|
.map-info-panel { |
|
|
|
position: absolute; |
|
|
|
|