Skip to content

电子围栏效果

示例

示例源码
vue
<template>
  <div style="height: 500px" class="vw-full vh-full">
    <div style="height: 400px">
      <mb-map
        :zoom="13"
        :center="mapCenter"
        :bearing="0"
        :pitch="45"
        @created="createdHandler"
      >
        <mb-tianditu-layer @created="add3DLayer" />
      </mb-map>
    </div>
  </div>
</template>
<script setup lang="ts">
import * as THREE from 'three'
import type { MapboxInstance } from '@mapbox-vue3/core'
import type { CustomLayerInterface } from 'mapbox-gl'

const mapCenter: [number, number] = [116.390714, 39.916535]
const altitude = 0
let map: MapboxInstance

const createdHandler = (mapbox: MapboxInstance) => {
  map = mapbox
}
const add3DLayer = () => {
  loaderModels()
}
const loaderModels = () => {
  const modelAsMercatorCoordinate = map.mapboxgl.MercatorCoordinate.fromLngLat(
    mapCenter,
    altitude
  )

  // transformation parameters to position, rotate and scale the 3D model onto the map
  const modelTransform = {
    translateX: modelAsMercatorCoordinate.x,
    translateY: modelAsMercatorCoordinate.y,
    translateZ: modelAsMercatorCoordinate.z,
    /* Since our 3D model is in real world meters, a scale transform needs to be
     * applied since the CustomLayerInterface expects units in MercatorCoordinates.
     */
    scale: modelAsMercatorCoordinate.meterInMercatorCoordinateUnits(),
  }
  addThreeLayer(modelTransform, modelAsMercatorCoordinate)
}
const addThreeLayer = (modelTransform, modelAsMercatorCoordinate) => {
  const threeLayer = {
    id: '3d-model',
    type: 'custom',
    renderingMode: '3d',
    camera: null as unknown as THREE.PerspectiveCamera,
    scene: null as unknown as THREE.Scene,
    renderer: null as unknown as THREE.WebGLRenderer,
    onAdd(map, gl) {
      this.camera = new THREE.PerspectiveCamera()
      this.scene = new THREE.Scene()

      // fence range
      const boundaryArr = [
        [116.385394, 39.92098],
        [116.38591, 39.911632],
        [116.389942, 39.911565],
        [116.390457, 39.908734],
        [116.392174, 39.908733],
        [116.39226, 39.911959],
        [116.396294, 39.911894],
        [116.395691, 39.921438],
        [116.385394, 39.92098],
      ]

      const outer = boundaryArr
        .map((b) => getThreePoint(modelAsMercatorCoordinate, b))
        .map((p) => [p[0], -p[1], 0])
      const polygonMesh = addFencing(outer)
      polygonMesh.translateY(-200)
      this.scene.add(polygonMesh)

      {
        const skyColor = 0xb1e1ff // light blue
        const groundColor = 0xb97a20 // brownish orange
        const intensity = 3
        const light = new THREE.HemisphereLight(
          skyColor,
          groundColor,
          intensity
        )
        this.scene.add(light)
      }

      // use the Mapbox GL JS map canvas for three.js
      this.renderer = new THREE.WebGLRenderer({
        canvas: map.getCanvas(),
        context: gl,
        antialias: true,
      })

      this.renderer.autoClear = false
    },
    render(gl, matrix) {
      const m = new THREE.Matrix4().fromArray(matrix)
      const l = new THREE.Matrix4()
        .makeTranslation(
          modelTransform.translateX,
          modelTransform.translateY,
          modelTransform.translateZ
        )
        .scale(
          new THREE.Vector3(
            modelTransform.scale,
            -modelTransform.scale,
            modelTransform.scale
          )
        )

      this.camera.projectionMatrix = m.clone().multiply(l)
      this.camera.matrixWorldInverse = new THREE.Matrix4()
      this.renderer.resetState()

      this.renderer.render(this.scene, this.camera)
    },
  }
  map.addLayer(threeLayer as CustomLayerInterface)
}
const getThreePoint = (modelAsMercatorCoordinate, anotherPoint) => {
  const anotherPointModel = map.mapboxgl.MercatorCoordinate.fromLngLat(
    anotherPoint,
    altitude
  )
  const x =
    (anotherPointModel.x - modelAsMercatorCoordinate.x) /
    modelAsMercatorCoordinate.meterInMercatorCoordinateUnits()
  const y =
    (anotherPointModel.y - modelAsMercatorCoordinate.y) /
    modelAsMercatorCoordinate.meterInMercatorCoordinateUnits()
  return [x, y]
}
const addFencing = (points: number[][]) => {
  const height = 500

  const pointDistance: number[] = []
  const distance = points.reduce((totalDistance, point, index) => {
    let segmentDistance = 0
    if (index > 0) {
      const lastPoint = new THREE.Vector3(...points[index - 1])
      const currPoint = new THREE.Vector3(...point)
      segmentDistance = lastPoint.distanceTo(currPoint)
    }
    totalDistance += segmentDistance
    pointDistance.push(totalDistance)
    return totalDistance
  }, 0)

  const geometry = new THREE.BufferGeometry()
  const posArr: number[] = []
  const uvArr: number[] = []

  points.forEach((point, index) => {
    if (index == 0) return
    const lastPoint = points[index - 1]

    // 三角面1 triangle1
    posArr.push(...lastPoint)
    uvArr.push(pointDistance[index - 1] / distance, 0)
    posArr.push(...point)
    uvArr.push(pointDistance[index] / distance, 0)
    posArr.push(lastPoint[0], lastPoint[1], lastPoint[2] + height)
    uvArr.push(pointDistance[index - 1] / distance, 1)

    // 三角面2 triangle2
    posArr.push(...point)
    uvArr.push(pointDistance[index] / distance, 0)
    posArr.push(point[0], point[1], point[2] + height)
    uvArr.push(pointDistance[index] / distance, 1)
    posArr.push(lastPoint[0], lastPoint[1], lastPoint[2] + height)
    uvArr.push(pointDistance[index - 1] / distance, 1)
  })

  geometry.setAttribute(
    'position',
    new THREE.BufferAttribute(new Float32Array(posArr), 3)
  )
  geometry.setAttribute(
    'uv',
    new THREE.BufferAttribute(new Float32Array(uvArr), 2)
  )
  geometry.computeVertexNormals()

  const texture = new THREE.TextureLoader().load(
    `${__RESOURCE_URL__}images/wall.png`
  )
  texture.wrapS = THREE.RepeatWrapping
  texture.wrapT = THREE.RepeatWrapping

  // 材质
  const material = new THREE.MeshBasicMaterial({
    color: 0x00ff00,
    map: texture,
    transparent: true,
    opacity: 0.9,
    depthWrite: false,
    side: THREE.DoubleSide,
  })

  const mesh = new THREE.Mesh(geometry, material)
  return mesh
}
</script>