vue+OpenLayers 项目实践(七):追踪动画

「这是我参与11月更文挑战的第7天,活动详情查看:2021最后一次更文挑战」。

前言

由于最近项目需要,需要在vue项目中使用OpenLayers来进行 GIS 地图的开发,网上对 OpenLayers 文章并不算太多,借此机会分享下自己在项目中实际使用的一些心得。

本系列将陆续分享项目过程中实现的一些功能点。

本文基于前文的轨迹绘制vue+OpenLayers 项目实践(六):历史轨迹绘制,为其添加个动画追踪效果。

具体实现

本文需要实现的功能:

  • 丰富轨迹右下角卡片中的选项及按钮,添加自定义速度,开始结束暂停等功能按键;
  • 实现动态追踪效果

ok,接下来进行具体实现:

1,引入小车icon

首先我们引入一个小车图片:

image.png

在代码中添加小车的图层,初始位置在第一个坐标点。

// 绘制轨迹
drawRoute() {
  if (this.routeGeometry) {
    this.routeSource.clear();
  }
  this.routeGeometry = new LineString(this.routes);
  let route = new Feature(this.routeGeometry);
  // 绘制点
  let opints = this.drawPoint();
  // 添加小车
  let car = this.drawCar();
  this.routeSource.addFeatures([route, ...opints, car]);
  // 按轨迹边界缩放
  this.mapFit();
},
...

// 小车
drawCar() {
  const carMarker = new Feature({
    geometry: new Point(this.routeGeometry.getFirstCoordinate()),
  });
  let _style = new Style({
    image: new Icon({
      anchor: [0.5, 0.5],
      src: require("@/assets/img/car.png"),
      imgSize: [20, 36],
    }),
  });
  carMarker.setStyle(_style);
  this.carGeometry = carMarker;
  return carMarker;
},
复制代码

image.png OK,添加了一个绿色小车。

2. 拓展右下角控件

<div class="speed">
  速度:
  <div class="speed-input">
    <el-slider
      v-model="speed"
      :min="10"
      :max="1000"
      :step="10"
    ></el-slider>
  </div>
  <el-button type="primary" @click="changeAnimation">{{
    animationText
  }}</el-button>
</div>
复制代码

添加一个速度滑块控件。 image.png

3.动画实现

openlayers轨迹动画的实现原理:

  • 通过speed对现有路径进行分割,分割出大量的坐标点。
  • 通过监听postrender进行重新设置小车的坐标点来实现动画。

首先封装一下公共的方法:

  • 我们需要小车能够转向,所以需要计算出小车旋转的弧度
  • 计算出两个点位之间的总长度
  • 根据总长度及speed来将两个点位间插入分割点,返回一个新数组
/*
 * @Author: Shao Tao
 * @Date: 2021-11-19 13:38:44
 * @LastEditTime: 2021-11-19 15:53:14
 * @LastEditors: Shao Tao
 * @Description:
 * @FilePath: \vue-openlayers\src\utils\openlayers\route.js
 */
import { getDistance } from "ol/sphere";
import { transform } from "ol/proj";

/**
 * 根据坐标获取弧度
 */
export function getRotation(lng1, lat1, lng2, lat2) {
  let rotation = Math.atan2(lng2 - lng1, lat2 - lat1);
  return rotation;
}
/**
 * 计算坐标2点之间的距离
 */
export function formatLength(map, pointArray) {
  let length = 0;
  if (map.getView().getProjection().code_ == "EPSG:4326") {
    for (let i = 0, ii = pointArray.length - 1; i < ii; ++i) {
      let c1 = pointArray[i];
      let c2 = pointArray[i + 1];

      length += getDistance(c1, c2);
    }
  } else if (map.getView().getProjection().code_ == "EPSG:3857") {
    for (let i = 0, ii = pointArray.length - 1; i < ii; ++i) {
      let c1 = pointArray[i];
      let c2 = pointArray[i + 1];
      c1 = transform(c1, "EPSG:3857", "EPSG:4326");
      c2 = transform(c2, "EPSG:3857", "EPSG:4326");
      length += getDistance(c1, c2);
    }
  }
  return length;
}

/**
 * 计算两点之间的中间点
 * @param {*} map
 * @param {Array} pointDoubleArray 二维数组坐标
 * @param {num} speed 每个点之间的距离
 */
export function getCenterPoint(map, pointDoubleArray, speed) {
  speed = speed == undefined ? 10 : speed;
  let twolength = formatLength(map, pointDoubleArray);
  let rate = twolength / speed; //比例 默认10m/点
  let step = Math.ceil(rate); //步数(向上取整)
  let arr = new Array(); //定义存储中间点数组
  let c1 = pointDoubleArray[0]; //头部点
  let c2 = pointDoubleArray[1]; //尾部点
  let x1 = c1[0],
    y1 = c1[1];
  let x2 = c2[0],
    y2 = c2[1];
  for (let i = 1; i < step; i++) {
    let coor = new Array(2);
    coor[0] = ((x2 - x1) * i) / rate + x1;
    coor[1] = ((y2 - y1) * i) / rate + y1;
    arr.push(coor); //此时arr为中间点的坐标
  }
  arr.push(c2);
  return arr;
}
复制代码

接着修改Route.vue文件,在获取路径后将路径进行分割getRoutesAll

// 修改getList方法,调用getRoutesAll方法
getList() {
  ....
  this.getRoutesAll();
},

// 新增 分割路径点
getRoutesAll() {
  this.lastRouteIndex = 0;
  let _routesAll = [
    {
      coordinate: this.routes[0],
    },
  ];
  for (let i = 0, len = this.routes.length; i < len - 1; i++) {
    const item = this.routes[i];
    const itemNext = this.routes[i + 1];
    const rotation = getRotation(...item, ...itemNext);
    let points = getCenterPoint(this.openMap, [item, itemNext], this.speed);
    points = points.map((item) => {
      return {
        rotation,
        coordinate: item,
      };
    });
    _routesAll = [..._routesAll, ...points];
  }
  this.routesAll = _routesAll;
},
复制代码

这样我们就生成里一个包含弧度以及坐标的轨迹数组。然后实现点位移动。

changeAnimation() {
  this.animating ? this.stopAnimation() : this.startAnimation();
},
// 开始动画
startAnimation() {
  this.animating = true;
  this.lastTime = Date.now();
  this.animationText = "停止";
  this.routeLayer.on("postrender", this.moveFeature);
  this.carFeature.setGeometry(null);
},
// 停止动画
stopAnimation() {
  this.animating = false;
  this.animationText = "开始";
  this.carFeature.setGeometry(this.carGeometry);
  this.routeLayer.un("postrender", this.moveFeature);
},
// 移动动画
moveFeature(event) {
  // 具体移动动画方法
},
复制代码

实现moveFeature方法:

moveFeature(event) {
  const len = this.routesAll.length;
  if (this.lastRouteIndex < len - 1) {
    this.lastRouteIndex++;
  } else {
    this.lastRouteIndex = 0;
  }
  const current = this.routesAll[this.lastRouteIndex];
  this.carGeometry.setCoordinates(current.coordinate);
  const vectorContext = getVectorContext(event);
  let _style = new Style({
    image: new Icon({
      anchor: [0.5, 0.5],
      rotation: current.rotation,
      src: require("@/assets/img/car.png"),
      imgSize: [20, 36],
    }),
  });
  vectorContext.setStyle(_style);
  vectorContext.drawGeometry(this.carGeometry);
  this.openMap.render();
},
复制代码

ok,到此咱们就将整个轨迹移动实现了。

最终效果

map.gif

往期目录:

  1. vue+OpenLayers 项目实践(一):基本绘制与点击弹窗

  2. vue+OpenLayers 项目实践(二):多地图切换

  3. vue+OpenLayers 项目实践(三):Cluster 设置集群

  4. vue+OpenLayers 项目实践(四):设置围栏

  5. vue+OpenLayers 项目实践(五):历史围栏

  6. vue+OpenLayers 项目实践(六):历史轨迹绘制

最后

gitee 地址:gitee.com/shtao_056/v…

感谢大家阅读

如果能帮助到您,那更是我的万分荣幸。

后面应该会陆续的更新更多的功能使用,以及项目中遇到的一些问题,感兴趣的小伙伴可以点个收藏/关注。

猜你喜欢

转载自juejin.im/post/7032188908565692452