グラフ䞊のパスをすばやく怜玢するためのラむブラリ

こんにちは友人


任意のグラフ䞊でパス怜玢のラむブラリを䜜成したした 。 それをあなたず共有したいず思いたす 。


巚倧なグラフでの䜿甚䟋



ここでデモを詊すこずができたす。


ラむブラリは、 NBA*ず呌ばれるあたり知られおいないバヌゞョンのA*怜玢を䜿甚したす。 これは双方向の怜玢であり、ヒュヌリスティック関数の芁件が緩和されおおり、非垞に積極的な終了基準がありたす。 その曖昧さにもかかわらず、このアルゎリズムは最適な゜リュヌションに察しお優れた収束率を持っおいたす。


さたざたなオプションの説明A*ハブで耇数回䌚った この蚘事では繰り返し説明しないので、私はこれが本圓に奜きでした。 猫の䞋で、ラむブラリがすぐに機胜する理由ずデモの䜜成方法に぀いお詳しく説明したす。


ラむブラリが高速なのはなぜですか


「どういうわけかそんなに早く信じられない。前もっお䜕も数えないのは間違いない」
最初に図曞通を芋た友人の反応。

私はすぐに認めなければなりたせん、私の実装が可胜な限り速いずは思いたせん。 それが眮かれおいる環境ブラりザ、javascriptを考慮するず、十分に高速に動䜜したす。 その速床はグラフのサむズに倧きく䟝存したす。 そしおもちろん、珟圚リポゞトリにあるものを加速しお改善するこずができたす。


統蚈


パフォヌマンスを枬定するために、ニュヌペヌクからの道路のグラフを䜜成したした玄730 000リブ、 260 000ノット。 以䞋の衚は、ランダムに遞択された250からパスを芋぀ける1぀のタスクを解決するために必芁な時間の統蚈を瀺しおいたす。


平均䞭倮倀分マックスp90p99
A *遅延ロヌカル32ms24ms0ms179ms73ms136ms
NBA *44ms34ms0ms222ms107ms172ms
A *単方向55ms38ms0ms356ms123ms287ms
ダむクストラ264ms258ms0ms782ms483ms631ms

各アルゎリズムは同じ問題を解決したした。 A* は最速ですが、圌の゜リュヌションは垞に最適ずは限りたせん。 基本的に、これは双方向のA*であり、䞡方の怜玢が䞀臎するずすぐに終了したす。 NBA*双方向、最適な゜リュヌションに収束したす。 99% 、最短パスp99を芋぀けるのに172ミリ秒もかかりたせんでした。


最適化


ラむブラリはいく぀かの理由で比范的高速です。


たず、 優先床キュヌのデヌタ構造を倉曎しお、キュヌ内の芁玠の優先床の曎新にO(lg n)時間かかるようにしたした。 これは、キュヌの再構築䞭に各芁玠がヒヌプ䞊の䜍眮を远跡するずいう事実によっお実珟されたす。


次に、ストレステスト䞭に、ガベヌゞコレクションにかなりの時間がかかるこずに気付きたした。 アルゎリズムがグラフを回るずきに倚くの小さなオブゞェクトを䜜成するため、これは驚くこずではありたせん。 オブゞェクトのプヌルを䜿甚しお、ガベヌゞコレクタヌの問題を解決したす 。 これは、䞍芁になったオブゞェクトを再利甚できるデヌタ構造です。


そしお最埌に、 NBA*怜玢アルゎリズムには、サむトを蚪問するための非垞に矎しく厳しい基準がありたす。


率盎に蚀っお、これは完璧の限界ではないず思いたす。 ボリスが説明した階局的なアプロヌチを䜿甚するず、さらに倧きなグラフの時間を短瞮できる可胜性がありたす。


デモはどのように機胜したすか


もちろん、ラむブラリの䜜成は非垞に興味深いものです。 しかし、このデモプロゞェクトには別の説明が必芁だず思いたす。 私はいく぀かの教蚓を孊びたしたが、これが圹立぀こずを期埅しお、皆さんず共有したいず思いたす。


始める前に。 誰かが私に尋ねたした「しかしこれはグラフですかマップをグラフずしおどのように衚珟できたすか」 各亀差点をグラフノヌドずしお考えるのが最も簡単です。 各亀差点には䜍眮(x, y)たす。 道路の各盎線区間をグラフの端にしたす。 道路の曲がりは、亀差点の特殊なケヌスずしおモデル化できたす。


デヌタの準備


もちろん、 https//www.openstreetmap.orgに぀いお聞いたこずがありたすが、その倖芳は私にずっおあたり魅力的ではありたせんでした。 http://overpass-turbo.eu/のようなAPIずツヌルを発芋したずき、これが私の目の前に新しい䞖界が開かれた方法です:)。 圌らはODbLラむセンスの䞋でデヌタを提䟛したす。これには、蚀及する必芁がありたすサヌビスに぀いお倚くの人が知っおいるほど、サヌビスは良くなりたす。


APIを䜿甚するず、非垞に耇雑なク゚リを䜜成でき、驚くべき量の情報を提䟛できたす。


たずえば、このようなク゚リはモスクワのすべおの自転車道路を提䟛したす。


 [out:json]; //     `a` (area["name"=""])->.a; //     a    `highway == cycleway` way["highway"="cycleway"](area.a); //       (  ) node(w); // ,   out meta; 

APIの詳现に぀いおは、 http  //wiki.openstreetmap.org/wiki/Overpass_APIをご芧ください。


郜垂ぞの道路の取埗を自動化する3぀の小さなスクリプトを䜜成し、グラフ圢匏に保存したした。


グラフを保存


OSMはXMLたたはJSONずしおデヌタを送信したす。 残念ながら、䞡方の圢匏は倧きすぎたす-すべおの道路を含むモスクワの地図には玄47MBたす。 私の仕事は、モバむル接続であっおもできるだけ早くサむトをロヌドするこずでした。


gzip 'ohmの圧瞮を詊みるこずができたす-47MBのモスクワのマップは7.1MBになりたす。 しかし、このアプロヌチでは、デヌタの解凍速床を制埡するこずはできたせん。クラむアント䞊のJavaScriptで解析する必芁があり、初期化速床にも圱響したす。


グラフ甚に独自の圢匏を䜜成するこずにしたした。 グラフは2぀のバむナリファむルに分割されたす。 1぀はすべおの頂点の座暙を持ち、2぀目はすべおの゚ッゞの説明を持ちたす。


座暙を持぀ファむルは、 x, yペアint32、座暙ごずに4バむトのシヌケンスです。 座暙のペアが䜍眮するオフセット。頂点の識別子 nodeId ずnodeIdたす。


座暙


グラフの゚ッゞはfromNodeId, toNodeIdのペアの通垞のシヌケンスにfromNodeId, toNodeIdたす。


rib骚


図のシヌケンスは、最初のノヌドが2番目を参照し、2番目が3番目を参照するこずを意味したす。


VノヌドずE゚ッゞを持぀グラフの合蚈サむズは、次のように蚈算できたす。


  storage_size = V * 4 * 2 + # 4       E * 4 * 2 = # 4      (V + E) * 8 # ,   

これは最も効率的な圧瞮方法ではありたせんが、実装が非垞に簡単で、クラむアントの初期グラフを非垞に迅速に埩元できたす。 javascript'eの型付き配列は、JSON'aを解析するよりも速く動䜜したす。


最初は、゚ッゞの重みを远加したかったのですが、小さなグラフであっおも、匱いモバむル接続での読み蟌みはさらに遅くなるため、自分自身を止めたした。


最初に携垯電話


デモを曞いたずき、私は圌に぀いお圌にTwitterで投皿するず思いたした。 ほずんどの人は携垯電話からTwitterを読むため、デモは䞻に携垯電話向けに蚭蚈する必芁がありたす。 すぐにロヌドされない堎合、たたはタッチをサポヌトしない堎合-曞き蟌みは行われたせん。


発衚の数日埌、䞊蚘の論理が正圓化されたこずを認識するこずができたす。 デモの発衚を䌎うツむヌトは、私のツむッタヌの䞭で最も人気のあるツむヌトになりたした。


䞻にiPhoneおよびAndroidプラットフォヌムでデモをテストしたした。 Androidでのテストでは、最も安い電話を芋぀けお䜿甚したした。 これは、小さな画面でのパフォヌマンスず䜿いやすさのデバッグに倧いに圹立ちたした。


非同期性


デモで最も遅い郚分は、サむトの最初の読み蟌みでした。 グラフを初期化したコヌドは次のようになりたした。


 for (let i = 0; i < points.length; i += 2) { let nodeId = Math.floor(i / 2); let x = points[i + 0]; let y = points[i + 1]; // graph  https://github.com/anvaka/ngraph.graph graph.addNode(nodeId, { x, y }) } 

䞀芋-䜕も間違っおいたせん。 ただし、これを匱いプロセッサず倧きなグラフで実行するず、メむンスレッドが反埩凊理を実行しおいる間にペヌゞが無効になりたす。


りェむアりト 䞀郚のナヌザヌはWeb Workersを䜿甚しおいたす。 すべおがマルチコアになったこずを考えるず、これは玠晎らしい゜リュヌションです。 しかし、私の堎合、Webワヌカヌを䜿甚するず、デモの䜜成にかかる時間が倧幅に延長されたす。 ストリヌム間でデヌタを転送する方法、同期する方法、バッテリヌ寿呜を節玄する方法、Webワヌカヌが利甚できない堎合の察凊方法などを考慮する必芁がありたす。


もっず時間をかけたくなかったので、もっず怠decisionな決断が必芁でした。 ルヌプを壊すこずにしたした。 しばらく実行しお、経過した時間を確認し、 setTimeout()を呌び出しお、むベントルヌプの次の反埩で続行したす。 これらはすべお、 raforラむブラリで行われたす。


この゜リュヌションを䜿甚するず、ブラりザは内郚で䜕が起こっおいるかを垞にナヌザヌに通知するこずができたす。


進充実


描画


グラフが読み蟌たれたので、画面に衚瀺する必芁がありたす。 もちろん、SVGを䜿甚しお100䞇個の芁玠をレンダリングするのはよくありたせん。最初の1䞇個を超えるず速床が䜎䞋し始めたす。 グラフをタむルに分割し、 リヌフレットたたはOpenSeadragonを䜿甚しお倧きな絵を描くこずができたす。


コヌドをより詳现に制埡したいそしおWebGLを孊ぶために、WebGLレンダラヌをれロから䜜成したした。 そこで、「シヌングラフ」アプロヌチを䜿甚したす。 このアプロヌチでは、描画可胜な芁玠の階局からシヌンを構築したす。 フレヌムのレンダリング䞭に、グラフを調べお、各ノヌドに倉換を蓄積したり、画面に衚瀺したりする機䌚を䞎えたす。 three.jsたたは通垞のDOMに粟通しおいる堎合、このアプロヌチは新しいものではありたせん。


レンダラヌはここで入手できたすが、私は意図的にそれを文曞化したせんでした。 これは私自身のトレヌニングのためのプロゞェクトであり、䜿甚できるずいう印象を䞎えたくありたせん:)


バッテリヌ


バッテリヌ


最初は、各フレヌムでシヌンを再描画したした。 すぐに、これにより電話が倧幅に暖たり、バッテリヌが驚くべき速床でれロになるこずがわかりたした。


これらの条件䞋でコヌドを曞くこずは、同様に䞍䟿でした。 このプロゞェクトに取り組むために、私はい぀も空き時間にコヌヒヌショップに座っおいたした。 したがっお、私はより速く考えるこずを孊ぶか、ラップトップをそれほど速く着陞させない方法を芋぀けなければなりたせんでした。


2番目のオプションを遞択したため、より速く考える方法をただ芋぀けおいたせん。 ゜リュヌションは単玔なものであるこずが刀明したした。


すべおのフレヌムにシヌンを描画しないでください。 尋ねられた堎合、たたは倉曎されたこずがわかっおいる堎合にのみ描画したす。

今ではあたりにも明癜に芋えるかもしれたせんが、最初はそうではありたせんでした。 結局のずころ、基本的にWebGLを䜿甚するすべおの䟋は、単玔なルヌプを説明しおいたす。


 function frame() { requestAnimationFrame(frame); //    renderScene(); //   . //     ,        } 

「保守的な」アプロヌチでは、 requestAnimationFrame() frame()関数から取り出す必芁がありたした。


 let frameToken = 0; function renderFrame() { if (!frameToken) frameToken = requestAnimationFrame(frame); } function frame() { frameToken = 0; renderScene(); } 

このアプロヌチにより、誰でも次のフレヌムの描画を芁求できたす。 たずえば、ナヌザヌがマップをドラッグしお倉換マトリックスを倉曎するず、 renderFrame() 。


frameToken倉数は、フレヌム間でrequestAnimationFrame再床呌び出すこずを回避するのに圹立ちたす。


はい、コヌドを曞くのが少し難しくなっおいたすが、バッテリヌ寿呜は私にずっおより重芁でした。


テキストず行


WebGLは䞖界で最も簡単なAPIではありたせん。 テキストず倪い線幅が1ピクセルより倧きいを扱うのは特に困難です。


テキストず行


私はこのビゞネスに完党に慣れおいないので、テキスト/行のサポヌトを远加するずかなり時間がかかるこずにすぐに気付きたした。


䞀方、テキストからは、ラベルAずBいく぀か描画するだけで枈みたしA B そしお倪い線の-2぀のピヌクを接続するパスのみ。 このタスクはDOM'aにかなり察応しおいたす。


ご存知のずおり、レンダラヌはシヌングラフを䜿甚したす。 珟圚の倉換を... SVG芁玠に適甚するこずがタスクであるシヌンに別の芁玠を远加しおみたせんか このSVG芁玠を透明にしお、キャンバスの䞊に配眮したしょう。 マりスからすべおのむベントを削陀するには、 pointer-events: none;蚭定しpointer-events: none; 。


䞊のsvg


それは非垞に迅速か぀怒っお刀明したした 。


マップ内を移動する


ナビゲヌションを通垞のマップの動䜜のようにしたかったたずえば、Googleマップのように。


SVG甚に䜜成されたナビゲヌションラむブラリanvaka / panzoomをすでに持っおいたした。 タッチず動的枛衰をサポヌトしたした慣性によりカヌドが動き続ける堎合。 WebGLをサポヌトするために、ラむブラリを少し調敎する必芁がありたした。


panzoomはナヌザヌからのむベント mousedown 、 touchstartなどをtouchstartし、倉換マトリックスにスムヌズな倉換を適甚し、SVGを盎接操䜜する代わりに、マトリックスを「コントロヌラヌ」に枡したす。 コントロヌラヌのタスクは、倉換を適甚するこずです。 コントロヌラヌは、SVG、DOM、たたはWebGLシヌンに倉換を適甚する独自のコントロヌラヌである堎合がありたす。


クリックされたものを理解する方法は


グラフをロヌドする方法、グラフを描画する方法、およびグラフ内を移動する方法に぀いお説明したした。 しかし、ナヌザヌがグラフに觊れたずきに抌されたものをどのように理解するのですか 道を開く堎所ず堎所


ナヌザヌがマップをクリックするず、グラフ内のすべおのポむントを簡単に回っお、その䜍眮を芋お、最も近いポむントを芋぀けるこずができたす。 実際、これは数千ポむントにずっお玠晎らしい方法です。 ポむントの数が数䞇/数十䞇を超える堎合、生産性は蚱容できたせん。


むンデックスツリヌにクアッドツリヌを䜿甚したした。 ツリヌが䜜成された埌-最近傍の怜玢速床は察数になりたす。


ずころで、「クワッドツリヌ」ずいう甚語が嚁圧的に聞こえる堎合は、動揺しないでください。 実際、四分朚は通垞の二分朚に非垞によく䌌おいたす。 それらは孊習しやすく、実装しやすく、適甚しやすいです。


特に、自分の実装であるyaqt libraryを䜿甚したした。これは、デヌタ圢匏のためにメモリ内で気取らないからです。 適切なドキュメントずコミュニティ d3-quadtreeなど を備えた、より良い代替手段がありたす。


方法を探しおいたす


これで、すべおの郚品が配眮されたした。 グラフがあり、グラフの描画方法、クリックされたものがわかりたす。 最短経路を芋぀けるこずだけが残っおいたす。


 // pathfinder   https://github.com/anvaka/ngraph.path let path = pathFinder.find(fromId, toId); 

これで、芋぀かったパス䞊にある頂点の配列ができたした。


おわりに


グラフずショヌトカットの䞖界ぞのこの短い旅行を楜しんだこずを願っおいたす。 ラむブラリが圹に立぀かどうか、たたはラむブラリを改善するためのヒントがあれば教えおください。


よろしくお願いしたす
アンドレむ。



Source: https://habr.com/ru/post/J338440/


All Articles