前端使用 Konva 实现可视化设计器(14)- 折线 - 最优路径应用【代码篇】

话接上回《前端使用 Konva 实现可视化设计器(13)- 折线 - 最优路径应用【思路篇】》,这一章继续说说相关的代码如何构思的,如何一步步构建数据模型可供 AStar 算法进行路径规划,最终画出节点之间的连接折线。

请大家动动小手,给我一个免费的 Star 吧~

大家如果发现了 Bug,欢迎来提 Issue 哟~

github源码

gitee源码

示例地址

补充说明

上一章说到使用了开源 AStar 算法,它并不支持计算折线拐弯的代价,最终结果会出现不必要的拐弯,现已经把算法替换成自定义 AStar 算法,支持计算拐弯代价,减少了不必要的折线拐弯。

AStar 算法基本逻辑可以参考《C++: A*(AStar)算法》,本示例的自定义 AStar 算法,是在此基础上增加支持:格子代价、拐弯代价。

代码不长,可以直接看看:

关键要理解 AStar 算法的基本思路,特别是“open 和 closed 列表”、“每个节点的 f, g, h 值”

// src\Render\utils\aStar.ts

export interface Node {
   
  x: number
  y: number
  cost?: number
  parent?: Node
}

export default function aStar(config: {
   
  from: Node
  to: Node
  matrix: number[][]
  maxCost: number
}): Node[] {
   
  const {
    from, to, matrix, maxCost = 1 } = config

  const grid: Node[][] = matrixToGrid(matrix)

  const start = grid[from.y][from.x]
  const goal = grid[to.y][to.x]

  // 初始化 open 和 closed 列表
  const open: Node[] = [start]
  const closed = new Set<Node>()

  // 初始化每个节点的 f, g, h 值
  const f = new Map<Node, number>()
  const g = new Map<Node, number>()
  const h = new Map<Node, number>()
  g.set(start, 0)
  h.set(start, manhattanDistance(start, goal))
  f.set(start, g.get(start)! + h.get(start)!)

  // A* 算法主循环
  while (open.length > 0) {
   
    // 从 open 列表中找到 f 值最小的节点
    const current = open.reduce((a, b) => (f.get(a)! < f.get(b)! ? a : b))

    // 如果当前节点是目标节点,返回路径
    if (current === goal) {
   
      return reconstructPath(goal)
    }

    // 将当前节点从 open 列表中移除,并加入 closed 列表
    open.splice(open.indexOf(current), 1)
    closed.add(current)

    // 遍历当前节点的邻居
    for (const neighbor of getNeighbors(current, grid)) {
   
      // 如果邻居节点已经在 closed 列表中,跳过
      if (closed.has(neighbor)) {
   
        continue
      }

      // 计算从起点到邻居节点的距离(转弯距离增加)
      const tentativeG =
        g.get(current)! +
        (neighbor.cost ?? 0) +
        ((current.x === current.parent?.x && current.x !== neighbor.x) ||
        (current.y === current.parent?.y && current.y !== neighbor.y)
          ? Math.max(grid.length, grid[0].length)
          : 0)

      // 如果邻居节点不在 open 列表中,或者新的 g 值更小,更新邻居节点的 g, h, f 值,并将其加入 open 列表
      if (!open.includes(neighbor) || tentativeG < g.get(neighbor)!) {
   
        g.set(neighbor, tentativeG)
        h.set(neighbor, manhattanDistance(neighbor, goal))
        f.set(neighbor, g.get(neighbor)! + h.get(neighbor)!)
        neighbor.parent = current
        if (!open.includes(neighbor)) {
   
          open.push(neighbor)
        }
      }
    }
  }

  // 如果 open 列表为空,表示无法到达目标节点,返回 null
  return []

  // 数据转换
  function matrixToGrid(matrix: number[][]) {
   
    const mt: Node[][] = []

    for (let y = 0; y < matrix.length; y++) {
   
      if (mt[y] === void 0) {
   
        mt[y] = []
      }
      for (let x = 0; x < matrix[y].length; x++) {
   
        mt[y].push({
   
          x,
          y,
          cost: matrix[y][x]
        })
      }
    }

    return mt
  }

  // 从目标节点开始,沿着 parent 指针重构路径
  function reconstructPath(node: Node): Node[] {
   
    const path = [node]
    while (node.parent) {
   
      path.push(node.parent)
      node = node.parent
    }
    return path.reverse()
  }

  // 计算曼哈顿距离
  function manhattanDistance(a: Node, b: Node): number {
   
    return Math.abs(a
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值