You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Leaflet如何绘制沿两点方向固定长度的polyline折线

Leaflet固定长度方向对齐移动轨迹线实现方案

核心逻辑:不要直接连接新旧两个位置点,每次位置更新时,沿最近一次移动的方向,以最新位置为端点,反向推算固定长度对应的线段起点,最终仅绘制「推算起点-最新位置」的线段,即可保证线段长度固定、方向与移动方向完全一致。

具体实现步骤

  • 提前准备球面坐标计算工具方法:因为经纬度是WGS84球面坐标,不能直接用平面几何公式计算,以下方法基于大圆航线公式实现,无额外依赖,可直接使用。
  • 缓存上一次的物体位置,首次拿到位置时不绘制线段,仅做缓存。
  • 每次收到新位置时,先判断新旧点位是否重合,重合则跳过更新。
  • 计算旧点指向新点的方位角(即移动方向)。
  • 从新点出发,沿移动的反方向推算出距离新点恰好为固定长度的点,作为线段的起点。
  • 更新polyline的坐标为「推算起点、新位置」,再将新位置缓存为上一次位置,等待下一次更新。

可直接运行的代码

// 地球半径常量,单位米
const EARTH_RADIUS = 6378137;
// 配置:轨迹线固定长度,单位米
const FIXED_LINE_LENGTH = 100;

// 初始化地图上的轨迹线
const trackLine = L.polyline([], {
  color: '#f00',
  weight: 3,
  opacity: 0.9
}).addTo(map);

// 缓存上一次位置
let lastPos = null;

/**
 * 计算从起始点按指定方位角移动指定距离后的目标点经纬度
 * @param {L.LatLng} startPoint 起始经纬度点
 * @param {number} bearing 方位角,正北为0度,顺时针递增
 * @param {number} distance 移动距离,单位米
 * @returns {L.LatLng} 目标点经纬度
 */
function getDestinationPoint(startPoint, bearing, distance) {
  const angularDist = distance / EARTH_RADIUS;
  const bearingRad = bearing * Math.PI / 180;
  const startLatRad = startPoint.lat * Math.PI / 180;
  const startLngRad = startPoint.lng * Math.PI / 180;

  const targetLatRad = Math.asin(
    Math.sin(startLatRad) * Math.cos(angularDist) +
    Math.cos(startLatRad) * Math.sin(angularDist) * Math.cos(bearingRad)
  );
  const targetLngRad = startLngRad + Math.atan2(
    Math.sin(bearingRad) * Math.sin(angularDist) * Math.cos(startLatRad),
    Math.cos(angularDist) - Math.sin(startLatRad) * Math.sin(targetLatRad)
  );

  return L.latLng(
    targetLatRad * 180 / Math.PI,
    ((targetLngRad * 180 / Math.PI + 540) % 360) - 180
  );
}

/**
 * 计算a点指向b点的方位角
 * @param {L.LatLng} a 起始点
 * @param {L.LatLng} b 目标点
 * @returns {number} 0-360度范围的方位角
 */
function getBearing(a, b) {
  const latARad = a.lat * Math.PI / 180;
  const latBRad = b.lat * Math.PI / 180;
  const lngDiffRad = (b.lng - a.lng) * Math.PI / 180;

  const y = Math.sin(lngDiffRad) * Math.cos(latBRad);
  const x = Math.cos(latARad) * Math.sin(latBRad) - Math.sin(latARad) * Math.cos(latBRad) * Math.cos(lngDiffRad);
  const bearingRad = Math.atan2(y, x);
  return (bearingRad * 180 / Math.PI + 360) % 360;
}

/**
 * 位置更新时调用,传入新的经纬度即可更新轨迹线
 * @param {number} newLat 新纬度
 * @param {number} newLng 新经度
 */
function onPositionUpdate(newLat, newLng) {
  const newPos = L.latLng(newLat, newLng);
  // 首次定位仅缓存位置
  if (!lastPos) {
    lastPos = newPos;
    return;
  }
  // 点位无变化不更新
  if (newPos.equals(lastPos)) return;

  // 计算本次移动的方向角
  const moveBearing = getBearing(lastPos, newPos);
  // 从新点往移动反方向走固定长度,得到线段起点
  const lineStart = getDestinationPoint(
    newPos,
    (moveBearing + 180) % 360,
    FIXED_LINE_LENGTH
  );

  // 更新线的坐标
  trackLine.setLatLngs([lineStart, newPos]);
  // 缓存本次位置
  lastPos = newPos;
}

调整说明

如果需要轨迹线显示在物体移动方向的前方,只需要将lineStart的计算方位角改为moveBearing,同时将polyline坐标设置为[newPos, lineStart]即可。
该实现不受位置更新频率、两点实际移动距离影响,无论两次更新之间物体移动了10米还是1000米,绘制的线段始终保持设定的固定长度,方向与最近一次移动方向完全对齐。

内容的提问来源于stack exchange,提问作者Lucio Trucco

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.02 23:24:14