|
|
|
@ -35,9 +35,18 @@ |
|
|
|
@delete="deleteEntityFromContextMenu" |
|
|
|
@update-property="updateEntityProperty" |
|
|
|
/> |
|
|
|
<!-- 鼠标经纬度显示 --> |
|
|
|
<div class="coordinates-display"> |
|
|
|
{{ coordinatesText }} |
|
|
|
<!-- 地图右下角:比例尺 + 经纬度 --> |
|
|
|
<div class="map-info-panel"> |
|
|
|
<div class="scale-bar"> |
|
|
|
<span class="scale-bar-text">{{ scaleBarText }}</span> |
|
|
|
<div class="scale-bar-line" :style="{ width: scaleBarWidthPx + 'px' }"> |
|
|
|
<span class="scale-bar-tick scale-bar-tick-left"></span> |
|
|
|
<span class="scale-bar-tick scale-bar-tick-right"></span> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
<div class="coordinates-display"> |
|
|
|
{{ coordinatesText }} |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</div> |
|
|
|
</template> |
|
|
|
@ -85,7 +94,6 @@ export default { |
|
|
|
data() { |
|
|
|
return { |
|
|
|
viewer: null, |
|
|
|
scaleBar: null, |
|
|
|
// 绘制相关 |
|
|
|
drawingMode: null, // 'point', 'line', 'polygon', 'rectangle', 'circle' |
|
|
|
drawingHandler: null, |
|
|
|
@ -127,7 +135,10 @@ export default { |
|
|
|
image: { width: 150, height: 150 } |
|
|
|
}, |
|
|
|
// 鼠标经纬度 |
|
|
|
coordinatesText: '经度: --, 纬度: --' |
|
|
|
coordinatesText: '经度: --, 纬度: --', |
|
|
|
// 比例尺(高德风格) |
|
|
|
scaleBarText: '--', |
|
|
|
scaleBarWidthPx: 80 |
|
|
|
} |
|
|
|
}, |
|
|
|
components: { |
|
|
|
@ -2558,7 +2569,101 @@ export default { |
|
|
|
}) |
|
|
|
}, |
|
|
|
initScaleBar() { |
|
|
|
// ... 原有的比例尺代码保持不变 |
|
|
|
const that = this |
|
|
|
const update = () => { |
|
|
|
that.updateScaleBar() |
|
|
|
} |
|
|
|
update() |
|
|
|
this.viewer.camera.changed.addEventListener(update) |
|
|
|
this.viewer.camera.moveEnd.addEventListener(update) |
|
|
|
this.scaleBarCleanup = () => { |
|
|
|
that.viewer.camera.changed.removeEventListener(update) |
|
|
|
that.viewer.camera.moveEnd.removeEventListener(update) |
|
|
|
} |
|
|
|
}, |
|
|
|
/** 根据当前视口计算比例尺(兼容 2D/WebMercator,多位置尝试 pick + 视口宽度备用) */ |
|
|
|
getScaleBarInfo() { |
|
|
|
if (!this.viewer || !this.viewer.scene.canvas) return null |
|
|
|
const canvas = this.viewer.scene.canvas |
|
|
|
const w = canvas.clientWidth |
|
|
|
const h = canvas.clientHeight |
|
|
|
if (w <= 0 || h <= 0) return null |
|
|
|
const ellipsoid = this.viewer.scene.globe.ellipsoid |
|
|
|
const camera = this.viewer.camera |
|
|
|
const barPx = 80 |
|
|
|
const centerX = w / 2 |
|
|
|
// 多组参考点尝试(2D 下 pickEllipsoid 可能只在部分区域有效) |
|
|
|
const tryPoints = [ |
|
|
|
[centerX - barPx / 2, h - 50], |
|
|
|
[centerX + barPx / 2, h - 50] |
|
|
|
].concat([ |
|
|
|
[centerX - barPx / 2, h / 2], |
|
|
|
[centerX + barPx / 2, h / 2] |
|
|
|
]) |
|
|
|
let leftCartesian = null |
|
|
|
let rightCartesian = null |
|
|
|
leftCartesian = camera.pickEllipsoid(new Cesium.Cartesian2(tryPoints[0][0], tryPoints[0][1]), ellipsoid) |
|
|
|
rightCartesian = camera.pickEllipsoid(new Cesium.Cartesian2(tryPoints[1][0], tryPoints[1][1]), ellipsoid) |
|
|
|
if (!leftCartesian || !rightCartesian) { |
|
|
|
leftCartesian = camera.pickEllipsoid(new Cesium.Cartesian2(tryPoints[2][0], tryPoints[2][1]), ellipsoid) |
|
|
|
rightCartesian = camera.pickEllipsoid(new Cesium.Cartesian2(tryPoints[3][0], tryPoints[3][1]), ellipsoid) |
|
|
|
} |
|
|
|
if (leftCartesian && rightCartesian) { |
|
|
|
const rawMeters = Cesium.Cartesian3.distance(leftCartesian, rightCartesian) |
|
|
|
if (rawMeters > 0) { |
|
|
|
const niceMeters = this.niceScaleValue(rawMeters) |
|
|
|
const widthPx = Math.round((niceMeters / rawMeters) * barPx) |
|
|
|
const text = niceMeters >= 1000 ? `${(niceMeters / 1000).toFixed(0)} 公里` : `${Math.round(niceMeters)} 米` |
|
|
|
return { text, widthPx, niceMeters } |
|
|
|
} |
|
|
|
} |
|
|
|
// 2D/WebMercator 备用:用整屏宽度对应的地理范围计算(四角 pick 得到视口矩形) |
|
|
|
const leftBottom = camera.pickEllipsoid(new Cesium.Cartesian2(0, h - 20), ellipsoid) |
|
|
|
const rightBottom = camera.pickEllipsoid(new Cesium.Cartesian2(w, h - 20), ellipsoid) |
|
|
|
if (leftBottom && rightBottom) { |
|
|
|
const widthMeters = Cesium.Cartesian3.distance(leftBottom, rightBottom) |
|
|
|
if (widthMeters > 0) { |
|
|
|
const metersPerPx = widthMeters / w |
|
|
|
const rawMeters = metersPerPx * barPx |
|
|
|
const niceMeters = this.niceScaleValue(rawMeters) |
|
|
|
const widthPx = Math.round(niceMeters / metersPerPx) |
|
|
|
const text = niceMeters >= 1000 ? `${(niceMeters / 1000).toFixed(0)} 公里` : `${Math.round(niceMeters)} 米` |
|
|
|
return { text, widthPx: Math.min(120, Math.max(40, widthPx)), niceMeters } |
|
|
|
} |
|
|
|
} |
|
|
|
// 最后备用:从 2D 相机视锥估算(正交宽度 -> 米) |
|
|
|
if (this.viewer.scene.mode === Cesium.SceneMode.SCENE2D && camera.frustum && camera.frustum.right !== undefined) { |
|
|
|
const frustumWidth = camera.frustum.right - camera.frustum.left |
|
|
|
if (frustumWidth > 0) { |
|
|
|
const metersPerPx = frustumWidth / w |
|
|
|
const rawMeters = metersPerPx * barPx |
|
|
|
const niceMeters = this.niceScaleValue(rawMeters) |
|
|
|
const widthPx = Math.round(niceMeters / metersPerPx) |
|
|
|
const text = niceMeters >= 1000 ? `${(niceMeters / 1000).toFixed(0)} 公里` : `${Math.round(niceMeters)} 米` |
|
|
|
return { text, widthPx: Math.min(120, Math.max(40, widthPx)), niceMeters } |
|
|
|
} |
|
|
|
} |
|
|
|
return null |
|
|
|
}, |
|
|
|
/** 将实际距离圆整为易读的刻度值(米) */ |
|
|
|
niceScaleValue(meters) { |
|
|
|
const candidates = [10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 50000, 100000, 200000, 500000] |
|
|
|
let best = candidates[0] |
|
|
|
for (let i = 0; i < candidates.length; i++) { |
|
|
|
if (candidates[i] <= meters * 1.5) best = candidates[i] |
|
|
|
} |
|
|
|
return best |
|
|
|
}, |
|
|
|
updateScaleBar() { |
|
|
|
const info = this.getScaleBarInfo() |
|
|
|
if (info) { |
|
|
|
this.scaleBarText = info.text |
|
|
|
this.scaleBarWidthPx = Math.min(120, Math.max(40, info.widthPx)) |
|
|
|
} else { |
|
|
|
this.scaleBarText = '--' |
|
|
|
this.scaleBarWidthPx = 80 |
|
|
|
} |
|
|
|
this.$nextTick() |
|
|
|
}, |
|
|
|
initPointMovement() { |
|
|
|
// 创建屏幕空间事件处理器 |
|
|
|
@ -2657,10 +2762,6 @@ export default { |
|
|
|
}, Cesium.ScreenSpaceEventType.LEFT_UP) |
|
|
|
}, |
|
|
|
|
|
|
|
updateScaleBar() { |
|
|
|
// ... 原有的比例尺更新代码保持不变 |
|
|
|
}, |
|
|
|
|
|
|
|
// 初始化鼠标经纬度显示 |
|
|
|
initMouseCoordinates() { |
|
|
|
// 创建屏幕空间事件处理器 |
|
|
|
@ -2697,6 +2798,11 @@ export default { |
|
|
|
this.mouseCoordinatesHandler = null |
|
|
|
} |
|
|
|
|
|
|
|
if (typeof this.scaleBarCleanup === 'function') { |
|
|
|
this.scaleBarCleanup() |
|
|
|
this.scaleBarCleanup = null |
|
|
|
} |
|
|
|
|
|
|
|
if (this.viewer) { |
|
|
|
this.viewer.destroy() |
|
|
|
this.viewer = null |
|
|
|
@ -2737,20 +2843,72 @@ export default { |
|
|
|
display: none !important; |
|
|
|
} |
|
|
|
|
|
|
|
/* 鼠标经纬度显示样式 */ |
|
|
|
.coordinates-display { |
|
|
|
/* 地图右下角信息面板:比例尺在上、经纬度在下,整体略下移减少遮挡 */ |
|
|
|
.map-info-panel { |
|
|
|
position: absolute; |
|
|
|
bottom: 20px; |
|
|
|
right: 20px; |
|
|
|
bottom: 10px; |
|
|
|
right: 12px; |
|
|
|
display: flex; |
|
|
|
flex-direction: column; |
|
|
|
align-items: flex-end; |
|
|
|
gap: 6px; |
|
|
|
z-index: 1000; |
|
|
|
pointer-events: none; |
|
|
|
} |
|
|
|
|
|
|
|
/* 比例尺:高德风格,浅色底、圆角、刻度线 */ |
|
|
|
.scale-bar { |
|
|
|
background: rgba(255, 255, 255, 0.65); |
|
|
|
color: #333; |
|
|
|
padding: 4px 8px 6px; |
|
|
|
border-radius: 4px; |
|
|
|
box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12); |
|
|
|
display: flex; |
|
|
|
flex-direction: column; |
|
|
|
align-items: flex-start; |
|
|
|
min-width: 60px; |
|
|
|
} |
|
|
|
.scale-bar-text { |
|
|
|
font-size: 12px; |
|
|
|
font-family: 'Microsoft YaHei', 'PingFang SC', sans-serif; |
|
|
|
margin-bottom: 4px; |
|
|
|
color: #555; |
|
|
|
} |
|
|
|
.scale-bar-line { |
|
|
|
height: 2px; |
|
|
|
background: #555; |
|
|
|
position: relative; |
|
|
|
flex-shrink: 0; |
|
|
|
} |
|
|
|
.scale-bar-tick { |
|
|
|
position: absolute; |
|
|
|
top: 0; |
|
|
|
width: 2px; |
|
|
|
height: 6px; |
|
|
|
background: #555; |
|
|
|
} |
|
|
|
.scale-bar-tick-left { |
|
|
|
left: 0; |
|
|
|
top: -4px; |
|
|
|
} |
|
|
|
.scale-bar-tick-right { |
|
|
|
right: 0; |
|
|
|
left: auto; |
|
|
|
top: -4px; |
|
|
|
} |
|
|
|
|
|
|
|
/* 经纬度显示(在比例尺下方) */ |
|
|
|
.map-info-panel .coordinates-display { |
|
|
|
position: static; |
|
|
|
bottom: auto; |
|
|
|
right: auto; |
|
|
|
background-color: rgba(0, 0, 0, 0.7); |
|
|
|
color: white; |
|
|
|
padding: 8px 12px; |
|
|
|
padding: 6px 10px; |
|
|
|
border-radius: 4px; |
|
|
|
font-size: 14px; |
|
|
|
font-size: 13px; |
|
|
|
font-family: Arial, sans-serif; |
|
|
|
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3); |
|
|
|
z-index: 1000; |
|
|
|
pointer-events: none; |
|
|
|
min-width: 200px; |
|
|
|
text-align: right; |
|
|
|
} |
|
|
|
|