Проблема полягає в тому, щоб розібратися, на скільки згинати дуги, щоб підвищити їх візуальне дозвіл.
Ось одне рішення (серед безлічі можливих). Розглянемо всі дуги, що випливають із загального походження. Дуги тут найпотужніші. Щоб їх найкраще відокремити, давайте розставимо так, щоб вони розтікалися в однаково розташованих кутах . Проблема, якщо ми прорисуємо відрізки прямих до напрямків прямих ліній, оскільки, як правило, будуть кластери напрямків у різних напрямках. Скористаємося своєю свободою для згинання дуг, щоб максимально рівномірно розташувати кути, що відходять.
Для простоти давайте на карті використовувати кругові дуги. Природною мірою "вигину" в дузі від точки 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 на півдорозі між max та min; це хороший кандидат для стартового підшипника, оскільки він мінімізує максимальну кількість вигину, яка відбудеться . Отже, визначте
b_i = i * 360 / n - a0 - a_i:
це згинання у використанні .
Справа в елементарній геометрії, щоб намалювати кругову дугу від y до x, яка подає кут у 2 b_i, тому я пропускаю деталі та переходжу прямо до прикладу. Ось ілюстрації рішень для 64, 16 та 4 випадкових точок, розміщених у межах прямокутної карти
Як бачите, рішення, здається, стають приємнішими, оскільки кількість пунктів призначення збільшується. Рішення для n = 4 чітко показує, як підшипники однаково розташовані, оскільки в цьому випадку інтервал дорівнює 360/4 = 90 градусів, і очевидно, що точно досягнуто відстань.
Це рішення не є ідеальним: ви, ймовірно, можете виділити кілька дуг, які можна було вручну підлаштувати для покращення графіки. Але це не зробить жахливої роботи і, здається, це справді вдалий початок.
Алгоритм також має перевагу в тому, що він простий: найскладніша частина складається з сортування пунктів призначення за їхніми підшипниками.
Кодування
Я не знаю PostGIS, але, можливо, код, який я використовував для малювання прикладів, може послужити керівництвом для впровадження цього алгоритму в PostGIS (або будь-який інший ГІС).
Розглянемо наступне як псевдокод (але Mathematica виконає це :-). (Якщо цей сайт підтримував TeX, як це робить математика, статистика та TCS, я можу зробити це набагато читабельніше.) Позначення включає в себе:
- Імена змінних та функцій залежать від регістру.
- [Альфа] - грецький символ із малих букв. ([Pi] має значення, яке, на вашу думку, повинно мати.)
- x [[i]] - це елемент i масиву x (індексується починаючи з 1).
- f [a, b] застосовує функцію f до аргументів a і b. Функції у відповідному випадку, такі як "Min" та "Table", визначені системою; функції з початковою малою літерою, такі як "кути" та "зміщення", визначені користувачем. У коментарях пояснюються будь-які незрозумілі функції системи (наприклад, 'Arg').
- Таблиця [f [i], {i, 1, n}] створює масив {f [1], f [2], ..., f [n]}.
- Коло [o, r, {a, b}] створює дугу кола, центрованого в o радіусом r від кута a до кута b (обидва в радіанах проти годинникової стрілки з-за сходу).
- Порядок [x] повертає масив індексів відсортованих елементів x. x [[Замовлення [x]]] - це відсортована версія x. Коли y має таку ж довжину, що і x, y [[Порядок [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
починаючи під кутом \[Beta]
відносно підшипника x -> y.
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
]