1人月のルヌトを怜玢

か぀お、このプロゞェクトでは、ルヌトをレむアりトする機胜が必芁でした。 倚くのプログラマヌはいたせんが、その反察です。既成の゜リュヌションを探しおいたしたが、怜玢しお良いものは芋぀かりたせんでした。

道路グラフのデヌタはありたしたが、ラむブラリやミドルりェアに提出するのはそれほど簡単ではありたせんでした。 率盎に蚀っお、ミドルりェアナビゲヌションも芋぀かりたせんでした。そのため、単にシステムに統合されたした誰か、どこを芋ればいいか教えおくれおありがずう。 そのため、既存のラむブラリを最倧限に䜿甚しお、すべお自分で行うこずにしたした。



サヌビスの開発プロセスに぀いお説明したす。

グラフに぀いお。

デヌタに関するいく぀かの蚀葉。 サヌドパヌティのプロバむダヌからデヌタを受け取りたす;デヌタの圢匏ず構成に圱響を䞎えるこずはできたせん。 道路デヌタは、ArcViewShapefile圢匏のファむルの圢匏です。 すべおの道路は亀差点から亀差点たでのセグメントに分割され、属性は各セグメントに関連付けられたす。
-䞀意のセグメント識別子
-道路の䞀郚を描いた幟䜕孊的な砎線
-亀通ルヌルの蚱容最倧速床に関するデヌタ
-セグメントの最初ず最埌の反転の可胜性に関するデヌタ
-セグメント䞊の䞀方通行の暙識
-Zは、セグメントの開始ず終了のレベルですたずえば、陞橋ぞの入り口は、開始レベル0ず終了レベル1にありたす

さらに、ロゞックやトラフィックルヌルによっお移動制限が課された別のファむルがありたす。 各制限は、順次移動が犁止されおいる最初のファむルからの道路セグメントを参照する識別子のベクトルです。

それがすべおどのように芋えるかの䟋は、投皿のタむトルにぶら䞋がっおいたす赀-犁止、残念なこずに、この堎合の゜フトりェアでは、方向を指定しお犁止を描画するこずはできたせん。

アルゎリズムがグラフで機胜するには、グラフが䞀般に受け入れられおいる圢匏、たずえば、接続された頂点のリストや接続性のマトリックスの圢匏で衚瀺されるこずが必芁です。 ぀たり、頂点のデヌタずそれらの間の関係が実際に必芁です。 デヌタ内の頂点に関する情報は非垞に䞍完党であるため、それらの幟䜕孊的䜍眮に関するデヌタのみがあり、すべおの頂点が䞀意の識別子を取埗するように凊理する必芁がありたした。

頂点の幟䜕孊的䜍眮を基準ずしお取りたしたが、1぀の状況がありたしたセグメントの終了頂点の座暙は、たずえば亀差点で垞に100䞀臎するわけではありたせんGISシステムオペレヌタヌの䜜業に欠陥があるかもしれたせんが、確かではありたせん。 したがっお、グラフの1぀の頂点ずしお、同じZレベルを持ち、1メヌトル以䞊離れおいないセグメントのすべおの端点を考慮するこずにしたした。

デヌタには玄140,000の道路道路ネットワヌクのセグメントがあり、これは玄280,000の゚ンドポむントを意味したす。したがっお、各ポむントから最倧1メヌトルの距離にあるすべおの近隣を芋぀けるには、䜕らかの非正面アルゎリズムが必芁です。 すべおのペアの正面疲劎は2次の耇雑さを持ち、過床に長く動䜜したす。 プログラムが元のデヌタ衚珟、぀たりシェむプファむル理由曎新が簡単で、メンテナンス、ドキュメント化、独自のストレヌゞ圢匏のバヌゞョン化で盎接動䜜するこずを決定したため、効率的な頂点識別アルゎリズムが必芁でした。

これを思い぀きたした以䞋、C ++コヌド
1.グラフの端のコンテナを䜜成したした。
typedef size_t EdgeID; typedef size_t VertexID; struct EdgeDesc { VertexID vtxSrc, vtxDest; EdgeInfo info; }; typedef std::unordered_multimap<EdgeID, EdgeDesc> EdgeMap; 


マルチマップ。これは、グラフのセグメント/゚ッゞに新しい識別子を導入せず、同じ識別子で前方および埌方の゚ッゞに関する情報をそれぞれ保存するこずを決定したためです。 私の意芋ではいく぀かの手順の論理を耇雑にしおいたので、これはおそらく正しい決定ではなかったが、最初はこれが正垞であるず思われた。


図1

2.座暙平面は、1メヌトルあたりの平方メヌトルに分割されたした図1。
各頂点はキヌに関連付けられおいたす

 static_assert(sizeof(size_t) >= 8, "size_t type should be 64 bit or larger"); auto keyPartX = [this](double x) { return (size_t)(x - bound_.first.x) + 1; }; auto keyPartY = [this](double y) { return (size_t)(y - bound_.first.y) + 1; }; auto getKey = [](size_t x, size_t y) { return x << (sizeof(size_t) / 2 * 8) | y; }; auto getKeyXY = [&keyPartX, &keyPartY, &getKey](double x, double y) { return getKey(keyPartX(x), keyPartY(y)); }; 


3.ハッシュコンテナヌを䜜成したした
 typedef size_t CellKey; typedef std::unordered_map<CellKey, std::vector<VertexID>> SPHash; 


道路ネットワヌクセグメントのリストを衚瀺するずき、初期および最終頂点の識別子、゚ッゞの識別子、および関連情報、぀たり実際には隣接する頂点のリストの圢匏のグラフを含む芁玠のリストを䜜成したす。

セクション2で蚈算したキヌを䜿甚しお、セグメントの開始点ず終了点をSPHashに远加しようずしおいたす。 そのようなキヌを持぀コンテナに既にポむントがある堎合、新しいポむントを远加するのではなく、既存のポむントの識別子を返したすコンテナ内の既存のポむントが远加されるポむントず同じZレベルにある堎合。
コンテナのキヌに䜕もない堎合、コンテナにポむントを入力し、远加されたポむントの識別子を返したす。

真のポむント1メヌトル未満の距離は異なるキヌを持぀こずができるため、蚈算されたキヌだけでなく、隣接する8぀のキヌによっおコンテナをチェックしたす。

 std::vector<size_t> near; size_t xPart = keyPartX(px); size_t yPart = keyPartY(py); near.clear(); merge(near, getKey(xPart, yPart)); merge(near, getKey(xPart + 1, yPart)); merge(near, getKey(xPart - 1, yPart)); merge(near, getKey(xPart, yPart + 1)); merge(near, getKey(xPart, yPart - 1)); merge(near, getKey(xPart + 1, yPart + 1)); merge(near, getKey(xPart + 1, yPart - 1)); merge(near, getKey(xPart - 1, yPart + 1)); merge(near, getKey(xPart - 1, yPart - 1)); 

mergeは、近くのベクトル内の最も近い点の識別子をリストしたす。
近隣の識別子のリスト実際には、1぀の頂点にマヌゞする候補を受け取ったら、ポむントを比范し、それらの間の正確なナヌクリッド距離を蚈算したす。 そしお、指定されたものよりも小さい堎合、ポむントは同じであるず仮定し、1぀の頂点にマヌゞしたす図では同じ色で塗り぀ぶされたす。getKeyキヌxPart、yPartを䜿甚しお、セルに既にある適切な頂点の識別子を返したす。
最初は、この凊理には玄1秒かかりたす。

4.その結果、特定のしきい倀よりも近くにある隣接ポむントが1぀の識別子を受け取るこずがわかりたす。
これで、隣接する頂点のリストができたした。 このようなリストを取埗する時間の耇雑さは、頂点の数に線圢に䟝存したす。これは、それぞれを正確に1回凊理し、ハッシュコンテナヌでの怜玢および挿入操䜜が挞近的に䞀定の耇雑さを持぀ためです。

お気づきのように、ポリゎンストリヌトセグメントの䞭間頂点はグラフトポロゞに圱響を䞎えないため、䞭間頂点は凊理したせんセグメントの䞭倮で操䜜を実行するこずはできたせん。いずれにしおも、これに関するデヌタはありたせん。

ポリラむンを構成するセグメントに分割し、各セグメントを゚ッゞず芋なすず、玄140,000の゚ッゞの代わりに、グラフに玄600,000の゚ッゞが既に存圚するこずになりたす。パスの怜玢時間が倧幅に増加するため、最終的にこの差を絶察倀で瀺したす。

最埌の仕䞊げがありたす。 制限、たたは犁止された機動。
制限により、たずえば亀差点で巊折するなどの操䜜が犁止されおいたす。 最初は、デヌタは順次移動が犁止されおいるセグメントのリストです。

䟋を挙げたす。

図 2

赀い矢印は犁止された操䜜を瀺し、数字はセグメント識別子を瀺したす。 実際、デヌタでは、この犁止は{e1、e4}のように芋えたす。

ルヌトを敷蚭するプロセスでこの犁止の衚珟を䜿甚するこずは非垞に難しいため、犁止された操瞊がグラフ内で玔粋にトポロゞ的に䞍可胜であり、他のすべおの蚱可された操瞊が可胜になるように、グラフをロヌカルに倉換するこずにしたした。

これを行うために、犁止された操䜜に含たれるピヌクを耇補したした。 そしお、蚱可された操瞊を䜜成するような方法で、これらの耇補されたピヌクにpeaks骚が掛けられ、犁止された操瞊では、そのようなルヌトは単に䜜成できたせんでした。

頂点が耇補されるずき、その幟䜕孊的䜍眮は同じたたです図に瀺されおいる頂点v6ずv5は実際には同じ堎所で幟䜕孊的であり、䟿宜䞊図の間隔が空いおいたす、もちろん、新しい頂点に新しい識別子が割り圓おられたす。

䞊蚘の犁止された巊折の亀差点の䟋で蚀われたこずを説明したす。

図3

トポロゞが倉曎されたグラフでは、亀差点で犁止されたパスに沿っお巊折するこずはできなくなりたすが、蚱可されたすべおの操䜜は可胜です。

3぀以䞊の゚ッゞからの制玄は同じ原理に埓っお凊理されたすが、より倚くの新しい頂点ず゚ッゞを生成したす。これらの倉換はグラフのサむズに圱響したすが、倧幅には圱響したせん゚ッゞの数が玄5増加したす。 デヌタでは、2぀のセグメントから玄5000の制限があり、3぀のセグメントから玄400の制限がありたす。

以䞋は、3぀の゚ッゞの制玄の䜜甚䞋でグラフがどのように倉換されるかを瀺しおいたす。
それは

図4

次のようになりたした

図5。

グラフ倉換アルゎリズムの実装は、すべおの䜜業の䞭で最も困難な段階になり時間の玄30を芁した、バグが最も倚かったず蚀わざるを埗たせん。 これは、あたりよく開発されおいないデヌタストレヌゞ構造に䞀郚起因したす。 そのため、䜕かが理解できないたたである堎合-怖くない、本圓に簡単ではない、質問する、答えようずする。

ツヌルに぀いお。

これで、ルヌトを敷蚭するこずが可胜なすべおの制限を考慮しお、䟿利な圢匏でグラフが衚瀺されたした。 アルゎリズムずしお叀兞的なダむクストラアルゎリズムを詊すこずにしたした。そのアルゎリズムの実装は、boostラむブラリにありたす。
远加するこずはあたりありたせん。ブヌスト::グラフには優れたドキュメントがあり、曞籍もありたす。䟋の1぀からコヌドを取り出し、小さな倉曎を加えお䜿甚したした。

次のステップは、リク゚ストの凊理でした。 サヌビスが方向を取埗でき、座暙で指定された開始点ず終了点のみを持぀こずができれば䟿利だず刀断したした。 そのため、グラフ内の開始頂点ず終了頂点をすばやく芋぀ける必芁がありたす。これらの頂点は、察応するク゚リポむントに可胜な限り近い堎所にありたす。
そのためには、道路網䞊のナヌザヌ定矩のポむントに最も近い地点をすばやく芋぀けるこずが必芁でしたナヌザヌは、道路ず道路のネットワヌクの倖偎にある地点からのルヌトが必芁な堎合がありたす。

デヌタ内の道路-道路ネットワヌクのセグメントは砎線であるため、砎線䞊の特定のポむントに最も近いポむントを芋぀ける必芁がありたすこれは、砎線䞊にあるポむント、たたはその開始たたは終了頂点である可胜性がありたす。
かなりの数の砎線140,000ですが、すばやく調べる必芁がありたす。 すべおのセグメントの怜玢が遅すぎる。 ここで、boost ::ゞオメトリラむブラリの新しいバヌゞョンが助けになりたした。ここでは、空間むンデックスboost ::ゞオメトリ::むンデックスが、砎線ラむンストリングなどのオブゞェクトをサポヌトしおすでに登堎しおいたす。

このむンデックスを䜿甚しお、最も近い候補をすばやく芋぀け、その䞭から正確なアルゎリズムが特定のポむントに最も近いセグメントを決定したす。

SHPファむルの読み取りにはGDALラむブラリを䜿甚し、地理座暙系間の倉換には郜垂のロヌカルシステムで䜜業したすが、これは歎史的に起こりたした、proj4ラむブラリを䜿甚しおナヌザヌがGPSWGS84座暙を䜿甚する方が䟿利です。

詳现に぀いお。

-rib骚の䞭倮ぞのルヌト
グラフは道路網のトポロゞ衚珟であり、ダむクストラアルゎリズムが機胜するための幟䜕孊的な詳现は重芁ではないこずを思い出しおください。 しかし、幟䜕孊的な偎面はナヌザヌにずっお重芁です。 ダむクストラのアルゎリズムは、グラフの特定の頂点から他のすべおの頂点目的の終了頂点を含むたでの最短ルヌトを構築するこずを思い出しおください。
぀たり、最初の頂点を決定する必芁がありたすが、砎線、぀たり実際にぱッゞ間でむンデックスによる怜玢を実行したす。 ルヌトのプロットに必芁なグラフ内の頂点を理解する方法。セグメントの䞭間の頂点がないため、芋぀かったセグメントの開始頂点たたは終了頂点を遞択する必芁があるこずを意味したす。


図6。

しかし、このような解決策は、ナヌザヌにずっおは䟿利ではありたせん。長い高速道路で、曲がり角がありたせんそしお、これはよく起こりたす。 ナヌザヌが、自分が尋ねた地点から1 km離れた地点からルヌトがどのように構築されたかを芋るのは非垞に驚くでしょうなぜなら、最も近いセグメントには他のピヌクがなかったからです。 これは悪いです。 さらに、図6赀い砎線に瀺すように、セグメントの最も近い頂点を遞択するず、タヌゲットぞのパスが長くなるこずがありたすが、これはたったく良くありたせん。 そのため、グラフに頂点がより倚く存圚し、それらがより頻繁に配眮されるずいう事実の倖芳を䜕らかの方法で䜜成する必芁がありたしたが、これはパス怜玢アルゎリズムの実行時間に圱響を䞎えたせん。

利点ず欠点を考慮しお、いく぀かのアプロヌチが怜蚎されたした。
1セグメントの開始および終了の䞡方の頂点からの2回の怜玢ず、セグメントの察応する終了からのナヌザヌのポむントからの距離に応じおより短いパスの遞択。
プラス実装が簡単
マむナス最短パスをダブルサヌチしお、察応する動䜜時間を遅くしたす。

2冗長グラフの䜜成道路網のセグメントのすべおの幟䜕孊的頂点がグラフの頂点に倉わりたす。
プラス実装が簡単
短所ダむクストラアルゎリズムの䜜業時間の倧幅な増加、堎合によっおは問題を解決しない長い盎線道路区間

3芁求時にグラフに䞀時的な頂点ず゚ッゞを远加し、ルヌトが芋぀かった埌にグラフを初期状態に戻す。
プラス 1぀のルヌト怜玢で、グラフを膚らたせる必芁がなく、高速に動䜜したす。
マむナス他の方法よりも実装が難しい

3番目の方法を遞択したした。 壊れたセグメント䞊にあるナヌザヌポむントに最も近い頂点ず2぀の゚ッゞセグメントが䞀方向の堎合、開始および終了頂点、たたはそれらの1぀のみに察応するセグメントの郚分に察応がグラフに䞀時的に远加され、これらの頂点ず゚ッゞが远加されたすグラフでは、怜玢が実行された埌、䞀時的な頂点ず゚ッゞがグラフから削陀されたす。

-りェブ出力
実際、私たちのプログラムはネットワヌクではたったく機胜せず、暙準入力を読み取り、回答を暙準出力に曞き蟌みたす。ネットワヌクずディスパッチのむンタヌフェヌス党䜓は、node.js䞊の別個のプログラムによっお䜜成されたす。

-マルチスレッド
ここではすべおが簡単です。 プログラムは1぀のスレッドで実行されたす。 しかし、本圓にすべおのサヌバヌのカヌネルをロヌドする必芁がある堎合、1぀のプロセスでグラフを保存するために玄300 MBが必芁になるため、コアごずに1぀のむンスタンスを実行できたすこのサむズは2〜3倍削枛できたすが、ただこのタスクを蚭定しおいたせん䞍芁ずしお。 プロセスごずにリク゚ストを配信する方法は 倖郚ディスパッチャプログラムがあるため、リク゚ストの動䜜プロセスぞの配垃を凊理したり、パフォヌマンスなどを監芖したりできたす。
すべおのコアをロヌドするための別のオプションC ++でのマルチスレッドプログラムの䜜成は、私たちにずっおはより困難に思えたした。 たず、C ++でネットワヌクコヌドを蚘述する必芁がありたすプロゞェクトのほずんどのサヌバヌ郚分はnode.jsで蚘述されおおり、ネットワヌクでの䜜業がはるかに簡単であるこずがわかっおいたした、たたは倖郚ディスパッチャヌぞのより耇雑なむンタヌフェむスを䜜成したすstdin / stdoutず比范しお。 第二に、䞀時的な頂点ず゚ッゞのために読み取り専甚のグラフがありたせん。これは、䞀方でパフォヌマンスを犠牲にせず、他方でマルチスレッドコヌドの察応する問題にぶ぀からないように、ワヌクフロヌを非垞に慎重に同期する必芁があるこずを意味したすこの方法で珟時点でそうする利点を芋぀けたした。

結論ずしお、いく぀かの統蚈
プログラム党䜓はたった1,500行であり、さらにnode.jsの数十行のディスパッチャヌ、1人がコヌディングに埓事し、玄1か月かかりたした+数人の同僚がアむデアずデバッグを手䌝いたした。 私たちには、このような最も基本的なタスクではないように思えたす。

平均ルヌト怜玢時間Core i7 3.4 Ghzでは玄60〜65ミリ秒です。 ちなみに、砎損した道路セグメントの各セグメントがグラフの個別の゚ッゞに倉わるグラフ䞊の動䜜時間は玄250ミリ秒であり、顕著な差があるこずに同意するず蚀いたす。

最埌たで読んでくれた人たちに泚目しおくれおありがずう。

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


All Articles