环境

  • Vue:v3.5.13
  • Cesium:v1.127.0

需求描述

项目开发中需要实现地图比例尺功能,所以使用 Cesium 相关的 API 组合开发来实现,具体效果如下:

在这里插入图片描述

解决方法

下面直接贴代码:

<template>
  <div
    class="absolute left-4 bottom-4 pt-1 pb-2 pl-2 pr-2 bg-(--container-bg-3) text-white text-xs"
  >
    <div ref="scaleBarRef" class="scale-bar-view relative">{{ scaleBarText }}</div>
  </div>
</template>

<script setup lang="ts">
import { ref, toRaw, onMounted } from 'vue'
import * as Cesium from 'cesium'
import { useMapStore } from '@/stores/counter'

const mapStore = useMapStore()

const scaleBarRef = ref<HTMLElement | null>(null)
const scaleBarText = ref('0 km')

function updateScaleBar() {
  if (!mapStore.map) {
    console.error('mapStore.map 未初始化')
    return
  }

  const viewer = toRaw(mapStore.map)

  // 限制视图缩放高度
  const minHeight = 10 // 最小高度
  //const maxHeight = 10000000 // 最大高度
  const height = viewer.camera.positionCartographic.height
  if (height < minHeight) {
    viewer.camera.moveBackward(minHeight - height)
  }
  // else if(height > maxHeight) {
  //   viewer.camera.moveForward(height - maxHeight)
  // }

  const scene = viewer.scene
  const camera = scene.camera
  const canvas = scene.canvas

  // 屏幕中心点
  const center = new Cesium.Cartesian2(canvas.clientWidth / 2, canvas.clientHeight / 2)
  // 向右偏移 100px 的点
  const right = new Cesium.Cartesian2(center.x + 100, center.y)

  const pickCenter = camera.pickEllipsoid(center)
  const pickRight = camera.pickEllipsoid(right)

  if (!pickCenter || !pickRight) return

  const geoCenter = Cesium.Ellipsoid.WGS84.cartographicToCartesian(
    Cesium.Cartographic.fromCartesian(pickCenter),
  )
  const geoRight = Cesium.Ellipsoid.WGS84.cartographicToCartesian(
    Cesium.Cartographic.fromCartesian(pickRight),
  )

  const distance = Cesium.Cartesian3.distance(geoCenter, geoRight)

  // 将 100px 转换为真实距离
  const meterPerPixel = distance / 100

  // 动态计算适合的比例尺长度
  const scaleOptions = [
    1, 2, 5, 10, 20, 50, 100, 200, 500, 1000, 2000, 5000, 10000, 20000, 50000, 100000, 200000,
    500000, 1000000, 2000000, 5000000,
  ]

  // 设置最小和最大宽度
  const minWidthPx = 60
  const maxWidthPx = 150

  // 选择在 minWidthPx ~ maxWidthPx 范围内的最佳比例
  let finalDisplayDistance = scaleOptions[0]
  let finalWidthPx = finalDisplayDistance / meterPerPixel

  for (const opt of scaleOptions) {
    const width = opt / meterPerPixel
    if (width >= minWidthPx && width <= maxWidthPx) {
      finalDisplayDistance = opt
      finalWidthPx = width
    }
  }

  // 如果都不在范围内,就取最接近 max/min 的一个
  if (finalWidthPx < minWidthPx) {
    finalDisplayDistance =
      scaleOptions.find((opt) => opt / meterPerPixel > minWidthPx) || finalDisplayDistance
    finalWidthPx = finalDisplayDistance / meterPerPixel
  }

  if (finalWidthPx > maxWidthPx) {
    finalDisplayDistance =
      scaleOptions.reverse().find((opt) => opt / meterPerPixel < maxWidthPx) || finalDisplayDistance
    finalWidthPx = finalDisplayDistance / meterPerPixel
  }

  // 设置显示文本
  scaleBarText.value =
    finalDisplayDistance >= 1000 ? `${finalDisplayDistance / 1000} km` : `${finalDisplayDistance} m`

  if (scaleBarRef.value) {
    scaleBarRef.value.style.width = `${finalWidthPx}px`
    scaleBarRef.value.style.borderBottom = '2px solid #fff'
    scaleBarRef.value.style.textAlign = 'center'
  }
}

function initScaleBar() {
  if (!mapStore.map) {
    console.error('mapStore.map 未初始化')
    return
  }

  const viewer = toRaw(mapStore.map)
  // 每帧更新
  viewer.scene.postRender.addEventListener(updateScaleBar)
}

onMounted(() => {
  setTimeout(() => {
    initScaleBar()
  }, 300)
})
</script>

<style scoped lang="scss">
.scale-bar-view {
  &::before {
    content: ' ';
    position: absolute;
    left: -2px;
    bottom: -2px;
    height: 7px;
    width: 2px;
    background-color: #fff;
  }
  &::after {
    content: ' ';
    position: absolute;
    right: -2px;
    bottom: -2px;
    height: 7px;
    width: 2px;
    background-color: #fff;
  }
}
</style>

以上代码是一个使用 Cesium 实现的地图比例尺的 Vue 组件,其中样式是通过 tailwindcss 来设置的,viewer 对象是存储到全局状态中的,其余就没什么特殊的了。

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐