問題は、視覚的な解像度を向上させるために円弧をどれだけ曲げるかを把握することです。
これが1つの解決策です(可能な限り多くあります)。共通の起源から発生するすべてのアークを考えてみましょう。ここでアークが最も混雑します。それらを最適に分離するには、等間隔の角度で広がるように配置しましょう。通常、出発地から目的地まで直線セグメントを描画すると、さまざまな方向に目的地のクラスターが存在するため、問題です。出発角度を可能な限り均等に配置するために、自由にアークを曲げてみましょう。
簡単にするために、地図上で円弧を使用します。点yから点xへの弧の「曲がり」の自然な尺度は、yでの方位とyからxへの直接の方位との差です。このような弧は、yとxの両方が存在する円の扇形です。基本ジオメトリは、曲げ角度がアークのincluded角の半分に等しいことを示しています。
アルゴリズムを説明するには、もう少し表記が必要です。ましょうyは原点である(地図上に投影されるように)およびlet X_1を、X_2、...、x_nに関する先のポイントです。a_iをyからx_iへの方位、i = 1、2、...、nになるように定義します。
準備段階として、ベアリング(すべて0〜360度)が昇順であると仮定します。これには、ベアリングを計算してから並べ替える必要があります。どちらも簡単なタスクです。
理想的には、アークのベアリングを、ある開始ベアリングに対して360 / n、2 * 360 / nなどに等しくすることが望まれます。したがって、目的のベアリングと実際のベアリングの差は、i * 360 / n -a_iに開始ベアリングa0を加えたものに等しくなります。最大の差はこれらのn個の差の最大値であり、最小の差は最小値です。a0を最大値と最小値の中間に設定してみましょう。これは、発生する最大曲げ量を最小化するため、開始ベアリングの適切な候補です。したがって、定義する
b_i = i * 360 / n - a0 -a_i:
これは使用する曲げです。
2 b_iの角度を定めるyからxまでの円弧を描くことは基本的なジオメトリの問題であるため、詳細をスキップして、例に進みます。長方形のマップ内に配置された64、16、および4つのランダムポイントのソリューションの図を次に示します。
あなたが見ることができるように、ソリューションが取得するように見えるよりよい先のポイントの数が増加するにつれて。n = 4 の解は、ベアリングの均等な間隔を明確に示しています。この場合、間隔は360/4 = 90度に等しく、明らかに間隔が正確に達成されています。
この解決策は完璧ではありません。おそらく、グラフィックを改善するために手動で調整できるいくつかのアークを識別することができます。しかし、それはひどい仕事をしないで、本当に良いスタートのようです。
このアルゴリズムには、シンプルであるというメリットもあります。最も複雑な部分は、目的地に応じて目的地をソートすることです。
コーディング
私はPostGISを知りませんが、おそらく例を描画するために使用したコードは、PostGIS(または他のGIS)でこのアルゴリズムを実装するためのガイドとして役立つ可能性があります。
以下は擬似コードであると考えてください(ただし、Mathematicaはそれを実行します:-)。(このサイトはTeXにサポートされている場合、数学、統計、およびTCSのものがそうであるように、私はこれを作ることができる多くの、より読みやすい。)表記は含まれています:
- 変数名と関数名は大文字と小文字が区別されます。
- [Alpha]は小文字のギリシャ文字です。([Pi]には、あるべきだと思う価値があります。)
- x [[i]]は、配列x(1から始まるインデックス)の要素iです。
- f [a、b]は、関数fを引数aおよびbに適用します。「Min」や「Table」などの適切なケースの関数はシステム定義です。「angles」や「offset」などの最初の小文字の関数は、ユーザー定義です。コメントは、あいまいなシステム機能(「Arg」など)を説明します。
- Table [f [i]、{i、1、n}]は配列{f [1]、f [2]、...、f [n]}を作成します。
- Circle [o、r、{a、b}]は、半径rのoを中心とする角度aから角度b(真東から反時計回りにラジアン単位)の円弧を作成します。
- Ordering [x]は、xのソートされた要素のインデックスの配列を返します。x [[Ordering [x]]]はxのソートされたバージョンです。yがxと同じ長さの場合、y [[Ordering [x]]]はyをxと並行してソートします。
コードの実行可能部分は、20行未満という非常に短いものです。これは、その半分以上が宣言オーバーヘッドまたはコメントであるためです。
地図を描く
z
宛先のリストでy
あり、発信元です。
circleMap[z_List, y_] :=
Module[{\[Alpha] = angles[y,z], \[Beta], \[Delta], n},
(* Sort the destinations by bearing *)
\[Beta] = Ordering[\[Alpha]];
x = z[[\[Beta] ]]; (* Destinations, sorted by bearing from y *)
\[Alpha] = \[Alpha][[\[Beta]]]; (* Bearings, in sorted order *)
\[Delta] = offset[\[Alpha]];
n = Length[\[Alpha]];
Graphics[{(* Draw the lines *)
Gray, Table[circle[y, x[[i]],2 \[Pi] i / n + \[Delta] - \[Alpha][[i]]],
{i, 1, Length[\[Alpha]]}],
(* Draw the destination points *)
Red, PointSize[0.02], Table[Point[u], {u, x}]
}]
]
x-> y方位に対する角度で始まる点から点x
への円弧を作成します。y
\[Beta]
circle[x_, y_, \[Beta]_] /; -\[Pi] < \[Beta] < \[Pi] :=
Module[{v, \[Rho], r, o, \[Theta], sign},
If[\[Beta]==0, Return[Line[{x,y}]]];
(* Obtain the vector from x to y in polar coordinates. *)
v = y - x; (* Vector from x to y *)
\[Rho] = Norm[v]; (* Length of v *)
\[Theta] = Arg[Complex @@ v]; (* Bearing from x to y *)
(* Compute the radius and center of the circle.*)
r = \[Rho] / (2 Sin[\[Beta]]); (* Circle radius, up to sign *)
If[r < 0, sign = \[Pi], sign = 0];
o = (x+y)/2 + (r/\[Rho]) Cos[\[Beta]]{v[[2]], -v[[1]]}; (* Circle center *)
(* Create a sector of the circle. *)
Circle[o, Abs[r], {\[Pi]/2 - \[Beta] + \[Theta] + sign, \[Pi] /2 + \[Beta] + \[Theta] + sign}]
]
原点からポイントのリストまでの方位を計算します。
angles[origin_, x_] := Arg[Complex@@(#-origin)] & /@ x;
ベアリングのセットの残差のミッドレンジを計算します。
x
ソートされた順序のベアリングのリストです。理想的には、x [[i]]〜2 [Pi] i / n。
offset[x_List] :=
Module[
{n = Length[x], y},
(* Compute the residuals. *)
y = Table[x[[i]] - 2 \[Pi] i / n, {i, 1, n}];
(* Return their midrange. *)
(Max[y] + Min[y])/2
]