前端使用 Konva 實現視覺化設計器(13)- 折線 - 最優路徑應用【思路篇】

xachary發表於2024-06-08

這一章把直線連線改為折線連線,沿用原來連線點的關係資訊。關於折線的計算,使用的是開源的 AStar 演算法進行路徑規劃,啟發方式為 曼哈頓距離,且不允許對角線移動。

請大家動動小手,給我一個免費的 Star 吧~

大家如果發現了 Bug,歡迎來提 Issue 喲~

github原始碼

gitee原始碼

示例地址

靈感來源主要來自於下面優秀的文章:

關聯線探究,如何連線流程圖的兩個節點

主要參考了:如何挑選連線點及其真正的出入口、演算法的選型。具體程式碼沒有仔細瞭解,畢竟佈局和元素的想法不一樣,沒必要參考程式碼。

路徑規劃之 A* 演算法

主要了解一下演算法的介紹。

歐式距離、曼哈頓距離、切比雪夫距離、Octile距離

主要了解一下 AStar 演算法的各種啟發方式的差異。

路徑規劃視覺化動畫

形象的感受路徑搜尋的差異。

至於演算法本身,在目前階段下不是必須深入分析,這裡應用為主。

最優路徑

image

參考這張圖,基於當前案例,可以把折線想象為路徑,目標就是查詢最優路徑,例如:
image

又或者:
image

上面明顯不是我們直覺最優的路徑選擇,如:

  • 太貼近節點了
  • 轉彎太多

更希望是這樣:
image
image

開啟除錯模式,來說說連線點的出入口:
image

人為地,距離”連線點“偏離一些,定義所謂的”出入口“(途中綠色的點),作為折線真的起點和終點。

把連線先移除,看看其他點:
image

一共定義了 3 種點:

  • 連線點(紅色)
  • 出入口(綠色)
  • 途徑點(藍色)

關於途徑點,是人為挑選的,主要(中心點除外)來自於圖中不同顏色區域(線框),這裡定義了 ?種區域:

  • 連線點最小區域

為什麼不叫節點區域呢?因為此前設計的連線點是動態的,它可以節點內部的其他位置,只是目前定義的都是上下左右邊緣而已。所以,它可能比節點區域更小。

image

  • 連線不可透過區域

image

  • 連線不可透過擴充套件區域

兩個區域共同所在的最小區域

image

  • 連線透過區域

image

  • 連線透過擴充套件區域

同理,兩個區域共同所在的最小區域

image

演算法建模(關鍵)

上面說了那麼多點和區域,最終目的就是為了建模,可供演算法使用。
這個模型,就是一個陣列矩陣 matrix,可以理解成一個格子地圖,如:
image

0 代表可透過,1 代表不可透過(稱之為“牆”吧),對應的陣列矩陣,就是

[
	[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
	[0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
	[0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
	[0, 0, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
	[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0],
	[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0],
	[0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 0],
	[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
	[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
]

計算結果是一個途徑格子座標陣列:

座標就是陣列1、2層下標,可以視作 x、y 軸。
image

[
	[5, 3],
	[6, 3],
	[7, 3],
	[8, 3],
	[8, 4],
	[8, 5]
]

主要問題來了,畢竟在這裡的畫板,不同於演算法示例那樣“走格子”,800x800 的畫布大小,不可能建一個 800x800 陣列矩陣,效能可吃不消,別說更大的畫布了。

所以,如何建模才是這個案例畫折線的關鍵!

這裡,拿一個大一點的例子說明:
image

既然,拿“畫素”當作格子不現實,可以拿“點”作為格子不就好了嗎?
image

陣列矩陣變成:

[
	[0, 0, 0, 0, 0, 0, 0, 0, 0],
	[0, 0, 0, 0, 0, 0, 0, 0, 0],
	[0, 0, 0, 0, 0, 0, 0, 0, 0],
	[0, 0, 0, 0, 0, 0, 0, 0, 0],
	[0, 0, 0, 0, 0, 0, 0, 0, 0],
	[0, 0, 0, 0, 0, 0, 0, 0, 0],
	[0, 0, 0, 0, 0, 0, 0, 0, 0]
]

這裡缺少了“牆”,哪些是牆?其實就是上面說的不可透過區域:
image

“牆”不同於連線點,需要補充一些點:
image

陣列矩陣變成(增加了 2 列、2 行):

[
	[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
	[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
	[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 
	[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
	[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
	[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
	[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 
	[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
	[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
]

然後給陣列矩陣設定“牆”:

這裡把 2 定義為牆,所以 0、1 均能透過,方便後面區分和理解。

[
	[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
	[0, 2, 2, 2, 0, 0, 0, 0, 0, 0, 0],
	[0, 2, 2, 2, 0, 0, 0, 2, 2, 2, 0], 
	[0, 2, 2, 2, 0, 0, 0, 2, 2, 2, 0],
	[0, 2, 2, 2, 0, 0, 0, 2, 2, 2, 0],
	[0, 2, 2, 2, 0, 0, 0, 2, 2, 2, 0],
	[0, 2, 2, 2, 0, 0, 0, 2, 2, 2, 0], 
	[0, 0, 0, 0, 0, 0, 0, 2, 2, 2, 0],
	[0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]
]

連線點、連線線的出入口不應該是“牆”,調整一下:

設定為 1,方便區分

image

起點:[2, 3]
終點:[8, 5]
image

現在交給演算法,計算結果得出:
image

就是:
image

畫成線:
image

主要思路就是如此,雖然不是完美的,請看:
image

原因主要是演算法並不知道拐彎的“代價”,暫且如此吧。
思路的介紹到此為止,下一章再說說程式碼大概是如何實現的。

More Stars please!勾勾手指~

原始碼

gitee原始碼

示例地址

相關文章