vue+openlayers: 实现高德地图——模拟导航效果

238 阅读5分钟

GIF 2024-6-24 9-56-42.gif

<template>
  <div>
    <div :id="mapId" class="map-x" style="width: 100%; height: 300px"></div>
  </div>
</template>
<script lang="ts">
import 'ol/ol.css'
import { Feature, Map, View } from 'ol'
import { Tile as TileLayer } from 'ol/layer'
import XYZ from 'ol/source/XYZ'
import { fromLonLat, transform } from 'ol/proj'
import VectorLayer from 'ol/layer/Vector'
import { Vector as VectorSource } from 'ol/source'
import { LineString, Polygon } from 'ol/geom'
import Point from 'ol/geom/Point.js'
import {
  Style,
  Circle as CircleStyle,
  Icon,
  Fill,
  RegularShape,
  Stroke,
  Text,
} from 'ol/style'
import {
  defineComponent,
  nextTick,
  onMounted,
  ref,
  unref,
  reactive,
  defineExpose,
} from 'vue'
import {
  getTrajectoryApi,
  getShipBerthInfoApi,
  getShipBerthInfoEndApi,
} from '@/api/crewInfo'
export default defineComponent({
  props: {
    // 地图id
    mapId: {
      type: String,
      default: 'map',
    },
  },
  emits: ["getTimeData"],
  setup(props, ctx) {
    const state = reactive({
      dotsData: [],
      map: null,
      shipTrackSource: null,
      shipTrackLayer: null,
      featureMove: null,
      carPoints: [], //车还要走的点
      routeIndex: 0, //当前小车所在的路段
      timer: null,
      coordinates: [],
      newCoordinates: [],
      carPoint: null,
      carLayer: null,
      idValue: '',
    })
    onMounted(() => {
      state.idValue = props.mapId as string
      initMap()
    })
    const initMap = () => {
      state.map = new Map({
        target: state.idValue,
        view: new View({
          center: fromLonLat([121.20256119692851, 27.85807498090228]),
          zoom: 14,
        }),
        layers: [
          new TileLayer({
            source: new XYZ({
              url: 'http://wprd0{1-4}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&style=7&x={x}&y={y}&z={z}',
            }),
            visible: true,
          }),
        ],
      })
      state.shipTrackSource = new VectorSource({
        wrapX: false,
      })
      state.shipTrackLayer = new VectorLayer({
        source: state.shipTrackSource,
        zIndex: 99,
        // name: 'shipTrack',
        style: function (feature, resolution) {
          var featureType = feature.get('featureType')
          if (featureType === 'shipTrackLine') {
            var styles = []
            let line_info = {
              color: 'green',
              width: 6,
              zIndex: 96,
            }
            styles.push(
              new Style({
                stroke: new Stroke(line_info),
              })
            )
            // 动态展示箭头 按需引入
            // const geometry = feature.getGeometry()
            // // 轨迹地理长度
            // const totalLength = geometry.getLength()
            // // 像素间隔步长
            // let step = 100
            // // 将像素步长转实际地理距离步长
            // let StepLength = step * resolution
            // // 箭头总数
            // let arrowNum = Math.floor(totalLength / StepLength)

            // const rotations = []
            // const distances = [0]
            // let index = 0
            // geometry.forEachSegment(function (start, end) {
            //   let dx = end[0] - start[0]
            //   let dy = end[1] - start[1]
            //   let rotation = Math.atan2(dy, dx)
            //   distances.unshift(Math.sqrt(dx ** 2 + dy ** 2) + distances[0])
            //   rotations.push(rotation)
            // })

            // for (let i = 1; i < arrowNum; i++) {
            //   let arrow_coor = geometry.getCoordinateAt((i * 1.0) / arrowNum)
            //   const d = i * StepLength
            //   const grid = distances.findIndex((x) => x <= d)
            //   styles.push(
            //     new Style({
            //       geometry: new Point(arrow_coor),
            //       image: new Icon({
            //         src: require('@/assets/map/arrow.png'),
            //         opacity: 1,
            //         anchor: [0.5, 0.5],
            //         rotateWithView: true,
            //         rotation: -rotations[distances.length - grid - 1],
            //         scale: 0.06,
            //       }),
            //       zIndex: 4,
            //     })
            //   )
            // }
            return styles
          }
        },
        // declutter: false
      })
      state.map.addLayer(state.shipTrackLayer)
    }
    const addPointAndView = async (data) => {
      //画轨迹线
      await drawLine(data)
      //开始动
      moveStart()
    }
    //轨迹线  把每个点连起来
    const drawLine = (data) => {
      state.shipTrackSource.clear()
      let line = new Feature({
        geometry: new LineString(data),
        featureType: 'shipTrackLine',
      })
      state.shipTrackSource.addFeature(line)
    }
    const drawBtmLine = () => {
      let line = new Feature({
        geometry: new LineString(state.coordinates),
      })

      let source = new VectorSource({
        features: [line],
      })
      let lineLayer = new VectorLayer({
        source: source,
        style: new Style({
          stroke: new Stroke({
            color: '#E0E0E0',
            width: 6,
            // zIndex: 90,
          }),
        }),
      })
      state.map.addLayer(lineLayer)
    }
    const drawStart = (data: any) => {
      let point = new Feature({
        geometry: new Point(data),
      })
      let source = new VectorSource({
        features: [point],
      })
      let lineLayer = new VectorLayer({
        source: source,
        style: new Style({
          image: new Icon({
                src: require('@/assets/map/start.png'),
                scale: 0.3,
                anchor: [0.5, 1],
              }),

        }),
      })
      state.map.addLayer(lineLayer)
    }
    const drawEnd = (data: any, time: any, isShowIcon: Boolean) => {
      let point = new Feature({
        geometry: new Point(data),
      })
      if (isShowIcon) {
            // 设置小车样式
            point.setStyle(
            new Style({
              image: new Icon({
                src: require('@/assets/map/ship.png'),
                scale: 1,
                anchor: [0.5, 0.5],
                // rotation: -state.countRotate(),
              }),
            })
          )
        }
      let source = new VectorSource({
        features: [point],
      })
      let lineLayer = new VectorLayer({
        source: source,
        style: new Style({
          image: new Icon({
                src: require('@/assets/map/end.png'),
                scale: 0.3,
                anchor: [0.5, 1],
                // rotation: -state.countRotate(),
              }),
          text: new Text({
            text: time,
            font: '14px sans-serif',
            scale: 1,
            offsetY: -50,
            placement: 'point',
            overflow: true,
            fill: new Fill({
              color: 'blue',
            }),
          }),
        }),
      })
      state.map.addLayer(lineLayer)
    }
    //创建小车这个要素
    const moveStart = () => {
      //坐标转换 方便计算下一个位置
      state.dotsData = state.coordinates.map((item) => {
        return transform(item, 'EPSG:3857', 'EPSG:4326')
      })
      //深复制车的位置,不在原数组改变,方便重新播放
      state.carPoints = JSON.parse(JSON.stringify(state.dotsData))
      // 当前的小车
      state.carPoint = new Feature({
        geometry: new Point(fromLonLat(state.carPoints[0])),
      })
      // 设置小车样式
      state.carPoint.setStyle(
        new Style({
          image: new Icon({
            src: require('@/assets/map/ship.png'),
            scale: 1,
            anchor: [0.5, 0.5],
            rotation: -countRotate(),
          }),
        })
      )
      let source = new VectorSource({
        features: [state.carPoint],
      })
      state.carLayer = new VectorLayer({
        source: source,
        zIndex: 999,
      })
      state.map.addLayer(state.carLayer)
      timeStart()
    }
    //计时器开始
    const timeStart = () => {
      state.timer = setInterval(() => {
        if (state.routeIndex + 1 >= state.carPoints.length) {
          //重头开始
          state.routeIndex = 0
          //移除要素
          state.carLayer.getSource().removeFeature(state.carPoint)
          clearInterval(state.timer)
          //重复运动
          addPointAndView(state.coordinates) //自动开启功能
          return
        }
        // 到达下一个点了 需要变化角度
        if (nextPoint() === state.carPoints[state.routeIndex + 1]) {
          state.routeIndex++
          state.carPoint.getStyle().getImage().setRotation(-countRotate())
          drawLine(state.newCoordinates)
        }
        //改变坐标点
        state.carPoint.getGeometry().setCoordinates(fromLonLat(state.carPoints[state.routeIndex]))
      }, 10)
    }
    //计算下一个点的位置
    //这里的算法是计算了两点之间的点   两点之间的连线可能存在很多个计算出来的点
    const nextPoint = () => {
      let routeIndex = state.routeIndex
      let p1 = state.map.getPixelFromCoordinate(
        fromLonLat(state.carPoints[routeIndex])
      ) //获取在屏幕的像素位置
      let p2 = state.map.getPixelFromCoordinate(
        fromLonLat(state.carPoints[routeIndex + 1])
      )
      let dx = p2[0] - p1[0]
      let dy = p2[1] - p1[1]
      //打印可见  在没有走到下一个点之前,下一个点是不变的,前一个点以这个点为终点向其靠近
      let distance = Math.sqrt(dx * dx + dy * dy) * 3
      if (distance <= 1) {
        return state.carPoints[routeIndex + 1]
      } else {
        let x = p1[0] + dx / distance
        let y = p1[1] + dy / distance
        let coor = transform(
          state.map.getCoordinateFromPixel([x, y]),
          'EPSG:3857',
          'EPSG:4326'
        )
        state.carPoints[routeIndex] = coor //这里会将前一个点重新赋值  要素利用这个坐标变化进行移动
        var newCoor = state.map.getCoordinateFromPixel([x, y])
        if (newCoor[0] && newCoor[1]) {
          var data = [...state.coordinates]
          data.splice(0, routeIndex + 1)
          state.newCoordinates = [newCoor, ...data]
          drawLine(state.newCoordinates)
        }
        return state.carPoints[routeIndex]
      }
    }
    //计算两点之间的角度  算旋转角度
    const countRotate = () => {
      let i = state.routeIndex,
        j = i + 1
      if (j === state.carPoints.length) {
        i--
        j--
      }
      let p1 = state.carPoints[i]
      let p2 = state.carPoints[j]
      // console.log(Math.atan2(p2[0] - p1[0], p2[1] - p1[1]),p2[0] - p1[0],p2[1] - p1[1])
      return Math.atan2(p2[1] - p1[1], p2[0] - p1[0])
    }
    const getData = (data) => {
      getTrajectoryApi(data).then((res: any) => {
        if (res.code == 0) {
          handleData(res.data)
        }
      })
    }
    const handleData = (dataInfo: any) => {
      var data = dataInfo.gj.map((item) => {
        return transform(item, 'EPSG:4326', 'EPSG:3857')
      })
      state.coordinates = data
      addPointAndView(state.coordinates)
      drawBtmLine()
      var lastPoint = state.coordinates[state.coordinates.length - 1]
      drawEnd(lastPoint, dataInfo.time, false)
      var firstPoint = state.coordinates[0]
      drawStart(firstPoint)
    }
    // 渔船停靠过程
    const handleTrack = (data: any) => {
      getShipBerthInfoApi(data).then((res: any) => {
        if (res.code == 0) {
          ctx.emit("getTimeData", res.data);
          handleData(res.data)
          drawPort(res.data.shipBerth)
        }
      })
    }
    // 绘制停泊区域
    const drawPort = (data: any) => {
      console.log(data, 'ppppp')
      var subject = data
      let coord = []
      if (subject.geoJson) {
        for (let l of JSON.parse(subject.geoJson)) {
          if (l.lon > 10000000) {
            coord.push(fromLonLat([l.lon / 10000000, l.lat / 10000000], 'EPSG:3857'))
          } else {
            coord.push(fromLonLat([l.lon, l.lat]))
          }
          
        }
      }
      let feature = new Feature({
        geometry: new Polygon([coord]),
      })
      let source = new VectorSource({
        features: [feature],
      })
      let lineLayer = new VectorLayer({
        source: source,
        // zIndex: 210,
        style: new Style({
          stroke: new Stroke({
            color: subject.lineColor || 'red',
            width: subject.lineWidth || 1,
          }),
          fill: new Fill({
            color: subject.fillColor,
          }),
          text: new Text({
            text: subject.name || '',
            font: '10px sans-serif',
            scale: 1,
            offsetY: subject.type == 1 ? -20 : 0,
            placement: 'point',
            overflow: true,
            fill: new Fill({
              color: 'red',
            }),
          }),
        }),
      })
      state.map.addLayer(lineLayer)
    }
    const handleEnd = (data: any) => {
      getShipBerthInfoEndApi(data).then((res: any) => {
        if (res.code == 0) {
          ctx.emit("getTimeData", res.data);
          drawPort(res.data.shipBerth)
          var lastPoint = transform([res.data.lon, res.data.lat], 'EPSG:4326', 'EPSG:3857')
          drawEnd(lastPoint, res.data.time, true)
        }
      })
    }
    const clear = () => {
      clearInterval(state.timer)
        let map = state.map
        state.shipTrackSource.clear()
        map.getLayers().forEach(function(lyr) {
            // 检查图层类型是否是地图层
            if (lyr.get('isBaseLayer')) {
                // 保留地图层
            } else {
                // 移除其他图层
                lyr.getSource().clear()
            }
        });
    }
    defineExpose({
      drawPort,
      getData,
      clear,
    })
    return {
      drawPort,
      getData,
      handleTrack,
      handleEnd,
      clear,
    }
  },
})
</script>

说明:coordinates格式

coordinates: [
    [121.20256119692851, 27.85807498090228],
    [121.19761950502657, 27.8452116688472],
    [121.18814081630589, 27.837813949040797],
    [121.183046617258, 27.835185998528193],
    [121.17734951444675, 27.837927439862685],
    [121.17780028082089, 27.839351604582934],
    [121.17818682986521, 27.841990178333713],
    [121.17547850965846, 27.842725270769535],
],
shipBerth: {
    "id": "1661894383035592706",
    "geoJson": "[{"lon":1211781670,"lat":278477070},{"lon":1211782190,"lat":278473800},{"lon":1211774570,"lat":278471470},{"lon":1211773700,"lat":278474770}]",
    "name": "小型船只停泊区域三",
    "shipBerthType": "0",
    "regionalCapacity": 30,
    "availableRegionalCapacity": 10,
    "fillColor": "rgba(0,128,0,1)",
    "lineWidth": "1",
    "lineColor": "rgba(0,0,255,1)",
    "category": "02",
    "subCategory": "0223",
    "type": "3",
    "fontSize": "14",
    "fontColor": "rgba(238, 234, 234, 1)",
    "resolution": "2.4035558703611457",
    "remark": "小型船只停泊区域三",
    "deptId": "330000",
    "deptName": "浙江省",
    "deptPath": "/330000",
    "delFlag": "0",
    "useType": 1
},