문제는 시각적 해상도를 향상시키기 위해 호를 얼마나 많이 구부릴지를 알아내는 것입니다.
여기에 하나의 솔루션이 있습니다 (가능한 많은 것 중에서). 공통 원점에서 나오는 모든 호를 고려해 봅시다. 호는 가장 혼잡합니다. 그것들을 최상으로 분리하기 위해, 그것들이 같은 간격의 각도로 퍼지도록 배열합시다 . 출발지에서 목적지까지 직선 선분을 그리면 문제가됩니다. 일반적으로 여러 방향으로 목적지 클러스터가 있기 때문입니다. 이탈 각도를 가능한 한 균일하게 배치하기 위해 자유 곡선을 사용하여 호를 구부려 봅시다.
간단하게하기 위해지도에 원호를 사용하겠습니다. 점 y 에서 점 x 까지의 호에서 "굽힘"의 자연스러운 척도는 y 에서의 베어링과 y 에서 x 까지의 베어링 간 차이 입니다. 이러한 호는 y 와 x가 모두 있는 원의 섹터입니다 . 기본 형상은 굽힘 각도 가 원호에 포함 된 각도의 절반 임을 나타냅니다 .
알고리즘을 설명하려면 조금 더 표기법이 필요합니다. 하자 y는 원점이 될 (지도에 투영로) 및하자 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 = 90도 같고위한 베어링 균등하게 이격되어 얼마나 명확 = 4 보여준다.
이 솔루션은 완벽하지 않습니다. 그래픽을 개선하기 위해 수동으로 조정할 수있는 여러 개의 호를 식별 할 수 있습니다. 그러나 그것은 끔찍한 일을하지 않으며 정말 좋은 시작 인 것 같습니다.
이 알고리즘은 단순하다는 장점도 있습니다. 가장 복잡한 부분은 베어링에 따라 대상을 정렬하는 것입니다.
코딩
PostGIS는 모르지만 예제를 그리는 데 사용한 코드는 PostGIS (또는 다른 GIS)에서이 알고리즘을 구현하기위한 가이드 역할을 할 수 있습니다.
다음은 의사 코드라고 생각하십시오 (그러나 Mathematica 는 그것을 실행합니다 :-). (이 사이트가 수학, 통계 및 TCS와 같이 TeX를 지원하는 경우, 이것을 훨씬 더 읽기 쉽게 만들 수 있습니다.) 표기법에는 다음이 포함됩니다.
- 변수 및 함수 이름은 대소 문자를 구분합니다.
- [알파]는 소문자 그리스 문자입니다. ([Pi]는 당신이 생각해야 할 가치가 있습니다.)
- x [[i]]는 배열 x의 요소 i입니다 (1부터 시작하여 색인화 됨).
- f [a, b]는 함수 f를 인수 a와 b에 적용합니다. 'Min'및 'Table'과 같은 적절한 경우 함수는 시스템 정의입니다. 'angles'및 'offset'과 같이 초기 소문자가있는 함수는 사용자 정의됩니다. 주석은 모호한 시스템 기능 (예 : 'Arg')을 설명합니다.
- Table [f [i], {i, 1, n}]은 배열 {f [1], f [2], ..., f [n]}을 만듭니다.
- 원 [o, r, {a, b}]은 각도 a에서 각도 b까지 반경 r의 o에 중심을 둔 원호를 만듭니다 (둘 다 동쪽에서 반 시계 방향으로 라디안으로 표시).
- Ordering [x]는 x의 정렬 된 요소의 인덱스 배열을 반환합니다. x [[Ordering [x]]]는 x의 정렬 버전입니다. y가 x와 길이가 같으면 y [[Ordering [x]]]는 x와 병렬로 y를 정렬합니다.
코드의 실행 부분은 절반 이상이 선언적인 오버 헤드이거나 주석이기 때문에 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
각도에서 시작 \[Beta]
> Y 베어링 -는 x에 대하여.
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]] ~ 2Pii / 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
]