openlayers 轨迹回放(历史轨迹)(postrender事件和render方法)

openlayers 轨迹回放(历史轨迹)(postrender事件和render方法)

本篇介绍一下使用openlayers轨迹回放(历史轨迹)(postrender事件和render方法)

1 需求

  • 轨迹回放(历史轨迹)

2 分析

上篇是使用定时器实现的,本篇使用postrender事件和render方法

  • 轨迹回放(历史轨迹),一般是一次性拿到所有坐标点,使用postrender事件和render方法

Map的render方法:当在下一个动画帧时渲染地图
Layer的postrender事件:图层被渲染后触发

3 实现

<template>
  <div id="map" class="map"></div>
  <div class="toolbar">
    <el-button type="primary" @click="handleClick">{{ animationFlag ? '停止' : '开始' }}</el-button>
    <el-input v-model.number="speed"></el-input><span>速度</span>
  </div>
</template>

<script setup lang="ts">
import { Feature, Map, View } from 'ol';
import { LineString, Point } from 'ol/geom';
import { Tile as TileLayer, Vector as VectorLayer } from 'ol/layer';
import { get } from 'ol/proj';
import { Vector as VectorSource, XYZ } from 'ol/source';
import { Circle, Fill, Icon, Stroke, Style } from 'ol/style';
import iconSrc from '@/assets/image/truck.png';

const projection = get('EPSG:4326');

const layerTypeMap = {
  vector: ['vec', 'cva'], // [矢量底图, 矢量注记]
  image: ['img', 'cia'], // [影像底图, 影像注记]
  terrain: ['ter', 'cta'] // [地形晕渲, 地形注记]
};

const map = shallowRef();
const vectorLayer = shallowRef();
const source = shallowRef<VectorSource>(new VectorSource());
const trace = ref();
const animationFlag = ref(false);
const speed = ref(50);
const lastTime = ref(Date.now());
const distance = ref(0);
const iconFeature = shallowRef();
const traceFeature = shallowRef();
const route = shallowRef();
const angle = ref(0);

onMounted(() => {
  initMap('image');
  initTraceAndFeature();
});

const initMap = (layerType = 'image') => {
  const key = '替换为天地图key';

  vectorLayer.value = new VectorLayer({
    source: source.value,
    style: new Style({
      stroke: new Stroke({
        color: 'rgba(228, 147, 87, 1)',
        width: 3
      })
    })
  });
  // c: 经纬度投影 w: 墨卡托投影
  const matrixSet = 'c';
  map.value = new Map({
    target: 'map',
    layers: [
      // 底图
      new TileLayer({
        source: new XYZ({
          url: `https://t{0-7}.tianditu.gov.cn/DataServer?T=${layerTypeMap[layerType][0]}_${matrixSet}&tk=${key}&x={x}&y={y}&l={z}`,
          projection
        })
      }),
      // 注记
      new TileLayer({
        source: new XYZ({
          url: `https://t{0-7}.tianditu.gov.cn/DataServer?T=${layerTypeMap[layerType][1]}_${matrixSet}&tk=${key}&x={x}&y={y}&l={z}`,
          projection
        })
      }),
      vectorLayer.value
    ],
    view: new View({
      center: [116.406393, 39.909006],
      projection: projection,
      zoom: 5,
      maxZoom: 17,
      minZoom: 1
    })
  });
};

const initTraceAndFeature = () => {
  trace.value = [
    [110, 30],
    [110.2, 30],
    [110.4, 30.2],
    [110.8, 30.4],
    [111, 31],
    [111.3, 31],
    [111.6, 31],
    [111.9, 31],
    [112, 31],
    [112.3, 31],
    [112.5, 31],
    [112.8, 31],
    [113, 31],
    [114, 31],
    [115.3, 32],
    [115.5, 32],
    [115.8, 31.8],
    [116, 31.4],
    [116.2, 31.1],
    [116.5, 30.5],
    [115, 30.2],
    [114, 29.8],
    [113, 29.6],
    [112, 29.4],
    [111, 30.2],
    [110, 30.4],
    [109, 30.6],
    [108, 31]
  ];
  angle.value = getAngle(trace.value[0], trace.value[1]);
  iconFeature.value = new Feature({
    geometry: new Point(trace.value[0])
  });
  const icon = new Icon({
    crossOrigin: 'anonymous',
    src: iconSrc,
    color: 'red',
    opacity: 1,
    width: 30,
    height: 30,
    rotation: angle.value
  });
  iconFeature.value.setStyle(
    new Style({
      image: icon
    })
  );
  traceFeature.value = new Feature({
    geometry: new LineString(trace.value)
  });

  source.value?.addFeatures([iconFeature.value, traceFeature.value]);
  route.value = new LineString(trace.value);
};

const handleClick = () => {
  if (!animationFlag.value) {
    startAnimation();
  } else {
    stopAnimation();
  }
  animationFlag.value = !animationFlag.value;
};

const startAnimation = () => {
  lastTime.value = Date.now();
  vectorLayer.value.on('postrender', move);
  // 触发地图渲染
  const geo = iconFeature.value.getGeometry().clone();
  iconFeature.value.setGeometry(geo);
};
const move = e => {
  const time = e.frameState.time;
  // 时间戳差(毫秒)
  const elapsedTime = time - lastTime.value;
  // 距离(其实是比例的概念)
  distance.value = distance.value + (speed.value * elapsedTime) / 1e6;
  if (distance.value >= 1) {
    distance.value = 0;
    iconFeature.value.getGeometry().setCoordinates(trace.value[0]);
    animationFlag.value = false;
    iconFeature.value.getStyle().getImage().setRotation(getAngle(trace.value[0], trace.value[1]));
    stopAnimation();
    return;
  }
  // 保存当前时间
  lastTime.value = time;
  // 上次坐标
  const lastCoord = iconFeature.value.getGeometry().getCoordinates();
  // 获取新位置的坐标点
  const curCoord = route.value.getCoordinateAt(distance.value);
  // 设置新坐标
  iconFeature.value.getGeometry().setCoordinates(curCoord);
	// 设置角度
  iconFeature.value.getStyle().getImage().setRotation(getAngle(lastCoord, curCoord));
  // 调用地图渲染
  map.value.render();
};

const stopAnimation = () => {
  vectorLayer.value.un('postrender', move);
};

const getAngle = (point1, point2) => {
  let arc = 0;
  if (point2 && point2.length && point1 && point1.length) {
    if (
      (point2[0] - point1[0] >= 0 && point2[1] - point1[1] >= 0) ||
      (point2[0] - point1[0] < 0 && point2[1] - point1[1] > 0)
    ) {
      arc = Math.atan((point2[0] - point1[0]) / (point2[1] - point1[1]));
    } else if (
      (point2[0] - point1[0] > 0 && point2[1] - point1[1] < 0) ||
      (point2[0] - point1[0] < 0 && point2[1] - point1[1] < 0)
    ) {
      arc = Math.PI + Math.atan((point2[0] - point1[0]) / (point2[1] - point1[1]));
    }
  }
  return arc;
};
</script>
<style scoped lang="scss">
.map {
  width: 100%;
  height: 100%;
}
.toolbar {
  position: absolute;
  top: 20px;
  left: 100px;
  display: flex;
  justify-content: center;
  align-items: center;
  .el-input {
    width: 100px;
    margin: 0 20px;
  }
}
</style>


4 总结

  • 动画比使用定时器丝滑
  • 40
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值