|
|
|
@ -46,7 +46,7 @@ |
|
|
|
|
|
|
|
<!-- 地图右下角:比例尺 + 经纬度 --> |
|
|
|
<div class="map-info-panel"> |
|
|
|
<div class="scale-bar"> |
|
|
|
<div class="scale-bar" @click="handleScaleClick"> |
|
|
|
<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> |
|
|
|
@ -89,6 +89,15 @@ export default { |
|
|
|
type: String, |
|
|
|
default: 'airspace' // 'airspace' or 'ranging' |
|
|
|
}, |
|
|
|
scaleConfig: { |
|
|
|
type: Object, |
|
|
|
default: () => ({ |
|
|
|
scaleNumerator: 1, |
|
|
|
scaleDenominator: 1000, |
|
|
|
unit: 'km', |
|
|
|
position: 'bottom-right' |
|
|
|
}) |
|
|
|
} |
|
|
|
}, |
|
|
|
watch: { |
|
|
|
drawDomClick: { |
|
|
|
@ -99,6 +108,14 @@ export default { |
|
|
|
// this.initMap() |
|
|
|
} |
|
|
|
} |
|
|
|
}, |
|
|
|
scaleConfig: { |
|
|
|
deep: true, |
|
|
|
handler(newVal) { |
|
|
|
if (newVal) { |
|
|
|
this.updateScaleFromConfig(newVal) |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
}, |
|
|
|
data() { |
|
|
|
@ -149,6 +166,10 @@ export default { |
|
|
|
// 比例尺(高德风格) |
|
|
|
scaleBarText: '--', |
|
|
|
scaleBarWidthPx: 80, |
|
|
|
useCustomScale: false, |
|
|
|
customScaleText: '', |
|
|
|
currentScaleUnit: 'm', |
|
|
|
isApplyingScale: false, |
|
|
|
// 定位相关 |
|
|
|
locateDialogVisible: false |
|
|
|
} |
|
|
|
@ -174,6 +195,102 @@ export default { |
|
|
|
this.destroyViewer() |
|
|
|
}, |
|
|
|
methods: { |
|
|
|
updateScaleFromConfig(config) { |
|
|
|
if (!config || !this.viewer) return |
|
|
|
|
|
|
|
const { scaleNumerator, scaleDenominator, unit } = config |
|
|
|
if (!scaleDenominator || scaleDenominator <= 0) return |
|
|
|
|
|
|
|
let displayValue = '' |
|
|
|
let metersPerPixel = 0 |
|
|
|
|
|
|
|
const standardDPI = 96 |
|
|
|
const pixelsPerCm = standardDPI * 0.393701 |
|
|
|
|
|
|
|
const scaleFactor = scaleDenominator / scaleNumerator |
|
|
|
|
|
|
|
switch (unit) { |
|
|
|
case 'm': |
|
|
|
displayValue = `1:${scaleDenominator}` |
|
|
|
metersPerPixel = scaleFactor / pixelsPerCm |
|
|
|
break |
|
|
|
case 'km': |
|
|
|
displayValue = `1:${scaleDenominator}` |
|
|
|
metersPerPixel = (scaleFactor * 1000) / pixelsPerCm |
|
|
|
break |
|
|
|
default: |
|
|
|
displayValue = `1:${scaleDenominator}` |
|
|
|
metersPerPixel = scaleFactor / pixelsPerCm |
|
|
|
} |
|
|
|
|
|
|
|
console.log('比例尺设置:', displayValue, '每像素米数:', metersPerPixel.toFixed(4)) |
|
|
|
|
|
|
|
this.isApplyingScale = true |
|
|
|
this.useCustomScale = true |
|
|
|
this.customScaleText = displayValue |
|
|
|
this.currentScaleUnit = unit |
|
|
|
|
|
|
|
this.applyScaleToCamera(metersPerPixel) |
|
|
|
this.updateScaleBar() |
|
|
|
this.$forceUpdate() |
|
|
|
|
|
|
|
setTimeout(() => { |
|
|
|
this.isApplyingScale = false |
|
|
|
}, 1000) |
|
|
|
}, |
|
|
|
|
|
|
|
applyScaleToCamera(metersPerPixel) { |
|
|
|
if (!this.viewer || !this.viewer.camera) return |
|
|
|
|
|
|
|
const canvas = this.viewer.scene.canvas |
|
|
|
const width = canvas.clientWidth |
|
|
|
const height = canvas.clientHeight |
|
|
|
|
|
|
|
if (width <= 0 || height <= 0) return |
|
|
|
|
|
|
|
const camera = this.viewer.camera |
|
|
|
const scene = this.viewer.scene |
|
|
|
|
|
|
|
if (scene.mode === Cesium.SceneMode.SCENE2D) { |
|
|
|
const frustumWidth = width * metersPerPixel |
|
|
|
camera.frustum.right = frustumWidth / 2 |
|
|
|
camera.frustum.left = -frustumWidth / 2 |
|
|
|
const frustumHeight = height * metersPerPixel |
|
|
|
camera.frustum.top = frustumHeight / 2 |
|
|
|
camera.frustum.bottom = -frustumHeight / 2 |
|
|
|
} else { |
|
|
|
const cameraPosition = camera.positionCartographic |
|
|
|
const groundWidth = width * metersPerPixel |
|
|
|
|
|
|
|
const fov = camera.frustum.fov || Cesium.Math.PI_OVER_THREE |
|
|
|
const aspectRatio = width / height |
|
|
|
const verticalFov = fov / aspectRatio |
|
|
|
|
|
|
|
const targetHeight = (groundWidth / 2) / Math.tan(fov / 2) |
|
|
|
|
|
|
|
const destination = Cesium.Cartesian3.fromRadians( |
|
|
|
cameraPosition.longitude, |
|
|
|
cameraPosition.latitude, |
|
|
|
targetHeight |
|
|
|
) |
|
|
|
|
|
|
|
camera.flyTo({ |
|
|
|
destination: destination, |
|
|
|
duration: 0.5, |
|
|
|
orientation: { |
|
|
|
heading: camera.heading, |
|
|
|
pitch: camera.pitch, |
|
|
|
roll: camera.roll |
|
|
|
} |
|
|
|
}) |
|
|
|
} |
|
|
|
}, |
|
|
|
handleScaleClick() { |
|
|
|
this.$emit('scale-click', { |
|
|
|
useCustomScale: this.useCustomScale, |
|
|
|
customScaleText: this.customScaleText |
|
|
|
}) |
|
|
|
}, |
|
|
|
preventContextMenu(e) { |
|
|
|
e.preventDefault(); |
|
|
|
}, |
|
|
|
@ -2766,6 +2883,10 @@ export default { |
|
|
|
initScaleBar() { |
|
|
|
const that = this |
|
|
|
const update = () => { |
|
|
|
if (!that.isApplyingScale) { |
|
|
|
that.useCustomScale = false |
|
|
|
that.customScaleText = '' |
|
|
|
} |
|
|
|
that.updateScaleBar() |
|
|
|
} |
|
|
|
update() |
|
|
|
@ -2806,10 +2927,12 @@ export default { |
|
|
|
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 } |
|
|
|
const metersPerPx = rawMeters / barPx |
|
|
|
const scaleRatio = this.calculateScaleRatio(metersPerPx) |
|
|
|
const pixelsPerCm = this.getPixelsPerCm() |
|
|
|
const widthPx = Math.min(120, Math.max(40, Math.round(pixelsPerCm))) |
|
|
|
const text = `1:${scaleRatio}${this.currentScaleUnit}` |
|
|
|
return { text, widthPx, niceMeters: rawMeters } |
|
|
|
} |
|
|
|
} |
|
|
|
// 2D/WebMercator 备用:用整屏宽度对应的地理范围计算(四角 pick 得到视口矩形) |
|
|
|
@ -2819,11 +2942,11 @@ export default { |
|
|
|
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 } |
|
|
|
const scaleRatio = this.calculateScaleRatio(metersPerPx) |
|
|
|
const pixelsPerCm = this.getPixelsPerCm() |
|
|
|
const widthPx = Math.min(120, Math.max(40, Math.round(pixelsPerCm))) |
|
|
|
const text = `1:${scaleRatio}${this.currentScaleUnit}` |
|
|
|
return { text, widthPx, niceMeters: widthMeters } |
|
|
|
} |
|
|
|
} |
|
|
|
// 最后备用:从 2D 相机视锥估算(正交宽度 -> 米) |
|
|
|
@ -2831,15 +2954,34 @@ export default { |
|
|
|
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 } |
|
|
|
const scaleRatio = this.calculateScaleRatio(metersPerPx) |
|
|
|
const pixelsPerCm = this.getPixelsPerCm() |
|
|
|
const widthPx = Math.min(120, Math.max(40, Math.round(pixelsPerCm))) |
|
|
|
const text = `1:${scaleRatio}${this.currentScaleUnit}` |
|
|
|
return { text, widthPx, niceMeters: frustumWidth } |
|
|
|
} |
|
|
|
} |
|
|
|
return null |
|
|
|
}, |
|
|
|
/** 计算比例尺比例 */ |
|
|
|
calculateScaleRatio(metersPerPx) { |
|
|
|
const standardDPI = 96 |
|
|
|
const pixelsPerCm = standardDPI * 0.393701 |
|
|
|
const metersPerCm = metersPerPx * pixelsPerCm |
|
|
|
let scaleRatio = 0 |
|
|
|
if (this.currentScaleUnit === 'km') { |
|
|
|
scaleRatio = Math.round(metersPerCm / 1000) |
|
|
|
} else { |
|
|
|
scaleRatio = Math.round(metersPerCm) |
|
|
|
} |
|
|
|
return scaleRatio |
|
|
|
}, |
|
|
|
/** 获取1厘米对应的像素数 */ |
|
|
|
getPixelsPerCm() { |
|
|
|
const standardDPI = 96 |
|
|
|
const pixelsPerCm = standardDPI * 0.393701 |
|
|
|
return pixelsPerCm |
|
|
|
}, |
|
|
|
/** 将实际距离圆整为易读的刻度值(米) */ |
|
|
|
niceScaleValue(meters) { |
|
|
|
const candidates = [10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 50000, 100000, 200000, 500000] |
|
|
|
@ -2850,13 +2992,19 @@ export default { |
|
|
|
return best |
|
|
|
}, |
|
|
|
updateScaleBar() { |
|
|
|
const info = this.getScaleBarInfo() |
|
|
|
if (info) { |
|
|
|
this.scaleBarText = info.text |
|
|
|
this.scaleBarWidthPx = Math.min(120, Math.max(40, info.widthPx)) |
|
|
|
if (this.useCustomScale && this.customScaleText) { |
|
|
|
this.scaleBarText = `${this.customScaleText}${this.currentScaleUnit}` |
|
|
|
const pixelsPerCm = this.getPixelsPerCm() |
|
|
|
this.scaleBarWidthPx = Math.min(120, Math.max(40, Math.round(pixelsPerCm))) |
|
|
|
} else { |
|
|
|
this.scaleBarText = '--' |
|
|
|
this.scaleBarWidthPx = 80 |
|
|
|
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() |
|
|
|
}, |
|
|
|
|