openlayers7实现导航轨迹效果

openlayers7实现类似高德路径轨迹效果

基于ol7实现线的轨迹渲染效果,附代码实现
效果如下:

轨迹路径效果

  • 高德效果
    在这里插入图片描述

  • 本文实现效果
    在这里插入图片描述

实现思路

  1. 创建矢量图层
  2. 设置矢量图层默认样式,设置默认样式不可见
  3. 动态更新矢量图层的线样式
  4. 基于canvas实现的线段轨迹效果

实现代码

  • main.js
import Feature from "ol/Feature.js";
import { fromLonLat } from "ol/proj";
import Map from "ol/Map.js";
import VectorSource from "ol/source/Vector.js";
import View from "ol/View.js";
import XYZ from "ol/source/XYZ.js";
import { Tile as TileLayer, Vector as VectorLayer } from "ol/layer.js";
import { getVectorContext } from "ol/render.js";
import LineString from "ol/geom/LineString.js";
import MultiLineString from "ol/geom/MultiLineString";
import { transform2D } from "ol/geom/flat/transform";
import { getLineOffsetDashPixelCoords, getDestination } from "./utils";
import { Style } from "ol/style";
import Stroke from "ol/style/Stroke.js";

const DEFAULT_LINE_WIDTH = 10 * window.devicePixelRatio; /* 轨迹线段默认宽度 */
const DEFAULT_LINE_OUT_WIDTH = DEFAULT_LINE_WIDTH * 1.2;

function rendererArrows(event) {
  const vectorContext = getVectorContext(event);
  const layer = event.target;
  const transform = vectorContext["transform_"];
  const ctx = vectorContext["context_"];
  const source = layer.getSource();
  const viewExtent = event.frameState.extent;

  source.forEachFeatureInExtent(viewExtent, (feature) => {
    const geometry = feature.getGeometry();
    let lineStrings;
    if (geometry instanceof MultiLineString)
      lineStrings = geometry.getLineStrings();
    else if (geometry instanceof LineString) lineStrings = [geometry];
    if (!lineStrings) return;

    lineStrings.forEach((lineString, index) => {
      const coords = lineString.getFlatCoordinates();
      const pixelCoordinates = transform2D(
        coords,
        0,
        coords.length,
        2,
        transform,
        []
      );
      let pixelCoords = [];
      for (let j = 0; j < pixelCoordinates.length; j += 2) {
        const x = pixelCoordinates[j];
        const y = pixelCoordinates[j + 1];
        pixelCoords.push([x, y]);
      }
      const dashPixelCoords = getLineOffsetDashPixelCoords(pixelCoords);

      ctx.lineCap = "round";
      ctx.lineJoin = "round";
      /* 绘制线段-外层 */
      ctx.lineWidth = DEFAULT_LINE_OUT_WIDTH;
      ctx.strokeStyle = "rgba(0,156,106,1)";
      ctx.beginPath();
      dashPixelCoords
        .filter((dash) => !dash[3])
        .forEach((dash, index) => {
          const [x, y] = dash;
          if (index === 0) ctx.moveTo(dash[0], dash[1]);
          ctx.lineTo(x, y);
        });
      ctx.stroke();

      /* 绘制线段-内层 */
      ctx.lineWidth = DEFAULT_LINE_WIDTH;
      ctx.strokeStyle = "rgba(1,194,125,1)";
      ctx.beginPath();
      dashPixelCoords
        .filter((dash) => !dash[3])
        .forEach((dash, index) => {
          const [x, y] = dash;
          if (index === 0) ctx.moveTo(dash[0], dash[1]);
          ctx.lineTo(x, y);
        });
      ctx.stroke();

      /* 绘制箭头 */
      ctx.lineWidth = 1.5 * window.devicePixelRatio;
      ctx.strokeStyle = "white";
      dashPixelCoords
        .filter((dash) => dash[3])
        .forEach((dash, index) => {
          const [x, y, angle] = dash;

          /* 反向找到一个点A,用A点与当前点计算箭头另外两个点 */
          const arrowCoord = getDestination([x, y], angle - 180, 5);
          var l = DEFAULT_LINE_WIDTH * 0.65;
          var a = Math.atan2(y - arrowCoord[1], x - arrowCoord[0]);

          var x3 = x - l * Math.cos(a + (45 * Math.PI) / 180); // θ=30
          var y3 = y - l * Math.sin(a + (45 * Math.PI) / 180);
          var x4 = x - l * Math.cos(a - (45 * Math.PI) / 180);
          var y4 = y - l * Math.sin(a - (45 * Math.PI) / 180);
          ctx.beginPath();

          ctx.moveTo(x3, y3);
          ctx.lineTo(x, y);
          ctx.lineTo(x4, y4);

          ctx.stroke();
        });
    });
  });
}

const lineString = new LineString([
  fromLonLat([89.911906, 41.148991]),
  fromLonLat([107.490031, 40.316507]),
  fromLonLat([118.124797, 36.918396]),
  fromLonLat([112.367961, 24.090737])
]);
const routeFeature = new Feature({
  geometry: lineString
});
routeFeature.setStyle(
  new Style({
    stroke: new Stroke({
      color: [0, 0, 0, 0]
    })
  })
);

const source = new VectorSource({
  features: [routeFeature]
});
const layer = new VectorLayer({ source });

layer.on("prerender", rendererArrows);

const map = new Map({
  target: document.getElementById("map"),
  view: new View({
    center: fromLonLat([108, 27]),
    zoom: 3,
    minZoom: 2,
    maxZoom: 19
  }),
  layers: [
    new TileLayer({
      source: new XYZ({
        url:
          "http://webrd0{1-4}.is.autonavi.com/appmaptile?lang=zh_cn&size=1&scale=1&style=8&x={x}&y={y}&z={z}"
      })
    }),
    layer
  ]
});
  • utils.js
const DASH_OFFSET = 50 * window.devicePixelRatio; /* 间隔,单位屏幕像素 */
export function lineDistance(from, to) {
  const [x1, y1] = from;
  const [x2, y2] = to;

  return Math.sqrt(Math.pow(x1 - x2, 2) + Math.pow(y1 - y2, 2));
}
/* 根据一个点按夹角、距离计算第二个点,不适用地理坐标系 */
export function getDestination(from, bearing, distance) {
  const [x, y] = from;
  const endX = Math.cos((bearing * Math.PI) / 180) * distance + x;
  const endY = Math.sin((bearing * Math.PI) / 180) * distance + y;
  return [endX, endY];
}
export const pathAngle = (points, toDegrees = false) => {
  const [
    [pointFromX = 0, pointFromY = 0],
    [pointToX = 0, pointToY = 0]
  ] = points;
  return toDegrees
    ? (Math.atan2(pointToY - pointFromY, pointToX - pointFromX) * 180) / Math.PI
    : Math.atan2(pointToY - pointFromY, pointToX - pointFromX);
};
function splitLinePoint(from, to, res, marginDistance) {
  let _distnace = lineDistance(from, to); /* 线段长度 */
  let _bearing = pathAngle([from, to], true); /* 线段夹角 */
  const offsetDistance = DASH_OFFSET - marginDistance;
  if (_distnace > offsetDistance) {
    const [endX, endY] = getDestination(from, _bearing, offsetDistance);
    res.push([endX, endY, _bearing, true]);
    return splitLinePoint([endX, endY], to, res, 0);
  } else {
    return _distnace + marginDistance;
  }
}

export function getLineOffsetDashPixelCoords(coordinates) {
  const destinationArray = [];
  let marginDistance = 0;
  for (let d = 0, len = coordinates.length - 1; d < len; d++) {
    const [from, to] = [coordinates[d], coordinates[d + 1]];
    destinationArray.push([...from, 0, false]);

    marginDistance = splitLinePoint(from, to, destinationArray, marginDistance);
    destinationArray.push([...to, 0, false]);
  }
  return destinationArray;
}

demo

  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Vue是一种流行的JavaScript框架,用于构建用户界面。OpenLayers是一个开源的JavaScript库,用于在web浏览器中显示交互式地图。结合Vue和OpenLayers,我们可以实现台风轨迹的可视化。 首先,我们需要获取台风的相关数据。可以从台风数据的API或其他数据源中获取实时或历史台风数据。数据通常包含台风的经纬度坐标和其他相关信息,如风力、风速等。 在Vue组件中,我们可以使用OpenLayers来显示地图。首先,在Vue组件中引入OpenLayers库,并在Vue的生命周期钩子中初始化地图。可以使用OpenLayers的地图视图类(MapView)来设置地图的中心坐标和缩放级别。 接下来,我们需要将台风的轨迹数据添加到地图上。可以使用OpenLayers的矢量图层(Vector Layer)来添加台风轨迹。将每个台风点的经纬度坐标转换为OpenLayers的几何对象,并将其添加到矢量图层中。 为了使台风轨迹更具交互性,可以在每个台风点上添加弹出窗口,显示该点的详细信息。可以使用OpenLayers的弹出窗口类(Overlay)和交互类(Interaction)来实现这一功能。 最后,根据需求,可以添加其他地图元素,如底图切换、比例尺、图例等,以增强用户体验。 总之,使用Vue和OpenLayers,我们可以方便地将台风轨迹可视化,并提供交互功能。这种方式可以帮助用户更直观地了解台风的路径和特征,从而提高对台风的认知和应对能力。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值