Як я можу за допомогою ArcGIS 10.1 знайти геодезичну рівновіддалену точку, визначену трьома точками?


12

Наприклад, у мене є координати для трьох базових точок на береговій лінії, і мені потрібно знайти координати точки біля берега, яка рівновіддалена від усіх трьох. Це проста вправа з геометрії, але всі вимірювання повинні враховувати геодезію.

Якщо я наближався до цього евклідовим чином, я міг би виміряти геодезичні контури, що з'єднують базові точки, знайти середини сторін результуючого трикутника та створити перпендикулярні ортодроми до кожного з цих контурів. Три локсодроми, мабуть, сходяться в рівновіддаленій точці. Якщо це правильний метод, у Arc має бути простіший спосіб зробити це.

Мені потрібно знайти О


Чи існують обмеження щодо відносних положень 3 балів? Зображення східного узбережжя, середня точка - самий далекий схід. Ваше рішення не працює, оскільки перпендикуляри не збігатимуться з офшорними. Я впевнений, що ми можемо придумати інші погані випадки!
mkennedy

Цікаво, чи можете ви використати проекцію збереження відстані та запустити обчислення звідти? progonos.com/furuti/MapProj/Normal/CartProp/DistPres/… Не впевнений в алгоритмі для цього, повинен бути один ... можливо, це барицентр: en.wikipedia.org/wiki/Barycentric_coordinate_system
Олексій Лейт,

Щоб знайти вирішення тісно пов’язаної проблеми, шукайте на нашому сайті "трилатерацію" . Крім того, gis.stackexchange.com/questions/10332/… є дублікатом, але не має адекватних відповідей (швидше за все, тому, що питання було задано плутати).
whuber

@mkennedy В принципі поганих випадків немає, лише числово нестабільні. Вони виникають, коли три базові точки є колінеарними; два розчини (за сферичною моделлю) трапляються на двох полюсах загальної геодезичної; в еліпсоїдальній моделі вони виникають поблизу, де ці полюси можна було б очікувати.
whuber

Використання тут локсодромів було б невірно: вони не є перпендикулярними бісектрисами. На кулі ці лінії будуть частинами великих кіл (геодезики), але на еліпсоїді вони будуть трохи відходити від геодезики.
whuber

Відповіді:


10

Ця відповідь розділена на кілька розділів:

  • Аналіз та скорочення проблеми , показуючи, як знайти потрібну точку за допомогою «консервованих» процедур.

  • Ілюстрація: робочий прототип , що дає робочий код.

  • Приклад , показуючи приклади рішень.

  • Підводні камені , обговорюючи потенційні проблеми та як з ними впоратися.

  • Впровадження ArcGIS , коментарі щодо створення користувальницького інструменту ArcGIS та де отримати необхідні процедури.


Аналіз та зменшення проблеми

Почнемо з того, що в (ідеально круглої) сферичній моделі завжди знайдеться рішення - фактично, саме два рішення. З огляду на базові точки A, B і C, кожна пара визначає свій "перпендикулярний бісектриса", який є набором точок, рівновіддалених від двох заданих точок. Цей бісектриса є геодезичним (велике коло). Сферична геометрія еліптична : будь-які дві геодезики перетинаються (у двох унікальних точках). Таким чином, точки перетину бісектриси AB та бісектриси BC - за визначенням - рівновіддалені від A, B і C, тим самим вирішуючи задачу. (Дивіться першу цифру нижче.)

Речі виглядають складніше на еліпсоїді, але оскільки це невелике збурення сфери, ми можемо очікувати подібної поведінки. (Аналіз цього може зайняти нас занадто далеко.) Складні формули, які використовуються (всередині ГІС) для обчислення точних відстаней на еліпсоїді, не є концептуальною складністю, однак: проблема в основному однакова. Щоб побачити, як насправді проста проблема, давайте викладемо її дещо абстрактно. У цьому твердженні "d (U, V)" посилається на справжню, повністю точну відстань між точками U і V.

Давши три точки A, B, C (як пари lat-lon) на еліпсоїді, знайдіть точку X, для якої (1) d (X, A) = d (X, B) = d (X, C) і ( 2) ця спільна відстань є якомога меншою.

Ці три відстані все залежить від невідомого X . Таким чином, різниці відстаней u (X) = d (X, A) - d (X, B) і v (X) = d (X, B) - d (X, C) є реально оціненими функціями X. Знову ж таки, дещо абстрактно, ми можемо зібрати ці відмінності в упорядковану пару. Ми також будемо використовувати (lat, lon) в якості координат для X, що дозволяє нам також вважати його впорядкованою парою, скажімо, X = (phi, лямбда). У цій установці функція

F (phi, лямбда) = (u (X), v (X))

це функція від частини двовимірного простору, що приймає значення у двовимірному просторі, і наша проблема зводиться до

Знайдіть усе можливе (phi, лямбда), для якого F (phi, лямбда) = (0,0).

Ось де абстракція окупається: існує багато чудового програмного забезпечення для вирішення цієї (чисто числової багатовимірної кореневої проблеми). Як це працює, ви пишете рутину для обчислення F , потім передаєте її до програмного забезпечення разом з будь-якою інформацією про обмеження на його введення ( phi повинен лежати між -90 і 90 градусів, а лямбда повинна лежати між -180 і 180 градусів). Це відкручує частину секунди і повертає (як правило) лише одне значення ( phi , лямбда ), якщо воно може знайти одне.

Є деталі, з якими можна обробитись, оскільки для цього є мистецтво: є різні способи вирішення, на вибір, залежно від того, як F "поводиться"; це допомагає "спрямовувати" програмне забезпечення, надаючи йому розумну вихідну точку для його пошуку (це один із способів отримати найближче рішення, а не будь-яке інше); і вам зазвичай потрібно вказати, наскільки точним ви хотіли б, щоб рішення було (тому воно знає, коли зупинити пошук). (Докладніше про те, що GIS-аналітики повинні знати про такі подробиці, які виникають у багатьох проблемах з ГІС, будь ласка, відвідайте рекомендації щодо тематики, яку слід включити до курсу «Комп’ютерна наука для геопросторових технологій» та подивіться у розділі «Різне» наприкінці. )


Ілюстрація: робочий прототип

Аналіз показує нам потрібно запрограмувати дві речі: неочищену первісну оцінку рішення та обчислення F сам.

Початкову оцінку можна зробити «сферичним середнім» з трьох базових точок. Це отримують, представляючи їх у геоцентричних декартових координатах (x, y, z), усереднюючи ці координати і проектуючи це середнє повернення до сфери та повторно виражаючи його у широті та довготі. Розмір сфери несуттєвий, і обчислення тим самим проводяться прямо: оскільки це лише вихідна точка, нам не потрібні еліпсоїдальні обчислення.

Для цього працюючого прототипу я використав Mathematica 8.

sphericalMean[points_] := Module[{sToC, cToS, cMean},
  sToC[{f_, l_}] := {Cos[f] Cos[l], Cos[f] Sin[l], Sin[f]};
  cToS[{x_, y_, z_}] := {ArcTan[x, y], ArcTan[Norm[{x, y}], z]};
  cMean = Mean[sToC /@ (points Degree)];
  If[Norm[Most@cMean] < 10^(-8), Mean[points], cToS[cMean]] / Degree
  ]

(Кінцева Ifумова перевіряє, чи не може середній показник чітко вказати довготу; якщо так, то він повертається до прямого середнього арифметичного широт і довгот його введення - можливо, не великий вибір, але принаймні допустимий. Для тих, хто використовує цей код для керівництва реалізацією, зауважте, що аргументи Mathematica ArcTan є зворотними порівняно з більшістю інших реалізацій: перший аргумент - координата x, другий - координата y, і він повертає кут, зроблений вектором ( х, у).)

Що стосується другої частини, тому що Mathematica - подібний ArcGIS і майже всі інші GIS - містить код для обчислення точних відстаней на еліпсоїді, писати майже нічого. Ми просто називаємо рутинний пошук кореневих програм:

tri[a_, b_, c_] := Block[{d = sphericalMean[{a, b, c}], sol, f, q},
   sol = FindRoot[{GeoDistance[{Mod[f, 180, -90], Mod[q, 360, -180]}, a] == 
                   GeoDistance[{Mod[f, 180, -90], Mod[q, 360, -180]}, b] ==
                   GeoDistance[{Mod[f, 180, -90], Mod[q, 360, -180]}, c]}, 
           {{f, d[[1]]}, {q, d[[2]]}}, 
           MaxIterations -> 1000, AccuracyGoal -> Infinity, PrecisionGoal -> 8];
   {Mod[f, 180, -90], Mod[q, 360, -180]} /. sol
   ];

Найбільш примітним аспектом цієї реалізації є те, як вона ухиляється від необхідності обмежувати широту ( f) і довготу ( q), завжди обчислюючи їх по модулю 180 і 360 градусів відповідно. Це дозволяє уникнути необхідності стримувати проблему (що часто створює ускладнення). Параметри керування MaxIterationsтощо налаштовуються, щоб цей код забезпечив максимально можливу точність.

Щоб побачити його в дії, давайте застосуємо його до трьох базових точок, наведених у відповідному питанні :

sol = tri @@ (bases = {{-6.28530175, 106.9004975375}, {-6.28955287, 106.89573839}, {-6.28388865789474, 106.908087643421}})

{-6.29692, 106.907}

Обчислені відстані між цим рішенням і трьома точками

{1450.23206979, 1450.23206979, 1450.23206978}

(це метри). Вони погоджуються через одинадцяту значущу цифру (що насправді занадто точне, оскільки відстані рідко точні до кращого на міліметр або близько того). Ось малюнок цих трьох точок (чорний), їх трьох взаємних бісектрис та рішення (червоний):

Фігура 1


Приклад

Щоб перевірити цю реалізацію та отримати краще розуміння того, як поводиться проблема, ось контурний графік кореневої середньої квадратної невідповідності у відстанях для трьох широко розташованих базових точок. (Розбіжність RMS виходить шляхом обчислення всіх трьох відмінностей d (X, A) -d (X, B), d (X, B) -d (X, C) і d (X, C) -d (X , A), усереднюючи їх квадрати та беручи квадратний корінь. Він дорівнює нулю, коли X вирішує задачу і в іншому випадку збільшується, коли X відходить від рішення, і таким чином вимірює, наскільки "ми близькі" до рішення в будь-якій точці. )

Малюнок 2

Базові точки (60, -120), (10, -40) і (45,10) показані червоною в цій проекції Плат Керрі; розчин (49.2644488, -49.9052992), який вимагав 0,03 секунди для обчислення, - жовтий. Його невідповідність RMS становить менше трьох нанометрів , незважаючи на те, що всі відповідні відстані становлять тисячі кілометрів. Темні області показують невеликі значення RMS, а світлі - високі.

На цій карті чітко видно, що інше рішення лежить поруч (-49.2018206, 130.0297177) (обчислюється в RMS двох нанометрів, встановлюючи початкове значення пошуку діаметрально протилежне першому рішенню.)


Підводні камені

Числова нестабільність

Коли базові точки будуть майже колінеарними і зближуються, усі рішення будуть майже на півсвіту і надзвичайно важко точно визначити. Причина полягає в тому, що невеликі зміни місця розташування по всьому світу - переміщення його до базових точок або віддалення від нього - спричинить лише неймовірно крихітні зміни різниць відстаней. Просто не вистачає точності та точності, вбудованих у звичайні обчислення геодезичних відстаней, щоб закріпити результат.

Наприклад, починаючи з базових точок (45,001, 0), (45, 0) та (44,999,0), які розділені вздовж основного меридіана лише на 111 метрів між кожною парою, triотримують рішення (11.8213, 77.745 ). Відстань від неї до базових точок - 8,127,964,998 77; 8,127,964,998 41; та 8,127,964,998 65 метрів відповідно. Вони згодні на найближчий міліметр! Я не впевнений, наскільки точним може бути цей результат, але я не був би принаймні здивований, якби інші реалізації повернули місця далекі від цього, показуючи майже як хорошу рівність трьох відстаней.

Час обчислення

Ці обчислення, оскільки вони передбачають значний пошук, використовуючи складні обчислення відстані, не швидкі, зазвичай вимагають помітної частки секунди. Про це потрібно знати в режимі реального часу.


Впровадження ArcGIS

Python є кращим сценарієм для ArcGIS (починаючи з версії 9). Пакет scipy.optimize має багатоваріантний rootfinder, rootякий повинен робити те, що FindRootробиться в коді Mathematica . Звичайно, ArcGIS сам пропонує точні еліпсоїдальні обчислення відстані. Потім решта - це всі деталі реалізації: визначте, як будуть отримані координати базової точки (від шару? Набраного користувачем? З текстового файлу? Від миші?) Та як буде представлений вихід (як координати відображається на екрані «як графічна точка» як новий об’єкт точки в шарі?), запишіть цей інтерфейс, перенесіть код Mathematica, показаний тут (прямо), і вам все буде налаштовано.


3
+1 Дуже ретельно. Я думаю, що вам, можливо, доведеться почати стягувати плату за це, @whuber.
Радар

2
@Radar Дякую Я сподіваюся, що люди придбають мою книгу, коли (коли-небудь) вона зрештою з’явиться :-).
whuber

1
Зробимо Білла ... Надішліть чернетку !!!

Відмінно! Але все ж здається, що аналітичне рішення було б можливим. Переставивши задачу в 3d декартовий простір з 3 точками A, B, C і E, де E - центр Землі. Далі знайдіть дві площини Plane1 і Plane2. Площина1 була б площиною, яка є нормальною для площиниABE і проходить через E, середню точку (A, B). Аналогічно, Plane2 була б площиною, нормальною для planetACE і проходячи через E, середню точку (C, E). ЛініяO, утворена перетином площини1 і площини2, представляє точки, рівновіддалені від 3-х точок. Ближче двох точок до A (або B або C), де лініяO перетинає сферу - точкаO.
Кірк Куйкендалл

Це аналітичне рішення @Kirk стосується лише сфери. (Перетини площин з еліпсоїдом ніколи не є перпендикулярними бісектрисами в метриці еліпсоїда, за винятком кількох очевидних виняткових випадків: коли вони меридіани або екватор.)
плута

3

Як зазначаєте, ця проблема виникає при визначенні морських кордонів; її часто називають проблемою "три точки", і ви можете озвучити це і знайти кілька паперів, що їх вирішують. Один з цих робіт є мною (!), І я пропоную точне і швидко конвергентне рішення. Дивіться розділ 14 http://arxiv.org/abs/1102.1215

Метод складається з наступних етапів:

  1. вгадайте триточку О
  2. використовувати O як центр азимутальної рівновіддаленої проекції
  3. проекту A, B, C, до цієї проекції
  4. знайдіть триточку в цій проекції, O '
  5. використовувати O 'як новий центр проекції
  6. повторюйте, поки О 'і О не збігаються

Необхідна формула триточкового рішення в проекції наведена в роботі. Поки ви використовуєте точну азимутальну рівновіддалену проекцію, відповідь буде точною. Конвергенція є квадратичною, тобто потрібно лише кілька ітерацій. Це майже напевно перевершить загальні методи кореневого пошуку, запропоновані @whuber.

Я не можу вам прямо допомогти з ArcGIS. Ви можете захопити мій пакет python для виконання геодезичних обчислень з https://pypi.python.org/pypi/geographiclib та кодування проекції на основі цього просто.


Редагувати

Проблему пошуку триточки у виродженому випадку @ whuber (45 + eps, 0) (45,0) (45-eps, 0) розглянув Кейлі в " Про геодезичні лінії на сплетеному сфероїді" , Філ. Маг. (1870), http://books.google.com/books?id=4XGIOoCMYYAC&pg=PA15

У цьому випадку триточку отримують, слідуючи за геодезикою (45,0) з азимутом 90 і знаходячи точку, в якій геодезична шкала зникає. Для еліпсоїда WGS84 ця точка дорівнює (-0.10690908732248, 89.89291072793167). Відстань від цієї точки до кожного (45,001,0), (45,0), (44,999) становить 10010287,665788943 м (у межах нанометра або близько того). Це приблизно на 1882 км більше, ніж оцінка Уубера (що якраз і показує, наскільки нестабільний цей випадок). Для сферичної землі тризначна, звичайно, буде (0,90) або (0, -90).

ДОДАТОК: Ось реалізація методу азимутального рівновіддалення за допомогою Matlab

function [lat, lon] = tripoint(lat1, lon1, lat2, lon2, lat3, lon3)
% Compute point equidistant from arguments
% Requires:
%   http://www.mathworks.com/matlabcentral/fileexchange/39108
%   http://www.mathworks.com/matlabcentral/fileexchange/39366
  lats = [lat1, lat2, lat3];
  lons = [lon1, lon2, lon3];
  lat0 = lat1;  lon0 = lon1; % feeble guess for tri point
  for i = 1:6
    [x, y] = eqdazim_fwd(lat0, lon0, lats, lons);
    a = [x(1), y(1), 0];
    b = [x(2), y(2), 0];
    c = [x(3), y(3), 0];
    z = [0, 0, 1];
    % Eq. (97) of http://arxiv.org/abs/1102.1215
    o = cross((a*a') * (b - c) + (b*b') * (c - a) + (c*c') * (a - b), z) ...
        / (2 * dot(cross(a - b, b - c), z));
    [lat0, lon0] = eqdazim_inv(lat0, lon0, o(1), o(2))
  end
  % optional check
  s12 = geoddistance(lat0, lon0, lats, lons); ds12 = max(s12) - min(s12)
  lat = lat0; lon = lon0;
end

Тестуючи це за допомогою Octave, я отримую

октава: 1> формат довгий
октава: 2> [lat0, lon0] = триточковий (41, -74,36,140, ​​-41,175)
lat0 = 15.4151378380375
lon0 = -162.479314381144
lat0 = 15.9969703299812
lon0 = -147.046790722192
lat0 = 16.2232960167545
lon0 = -147.157646039471
lat0 = 16.2233394851560
lon0 = -147.157748279290
lat0 = 16.2233394851809
lon0 = -147.157748279312
lat0 = 16.2233394851809
lon0 = -147.157748279312
ds12 = 3.72529029846191e-09
lat0 = 16.2233394851809
lon0 = -147.157748279312

як три точки для Нью-Йорка, Токіо та Веллінгтона.

Цей метод є неточним для сусідніх колінних точок, наприклад, [45.001,0], [45,0], [44.999,0]. У цьому випадку вам слід вирішити для M 12 = 0 на геодезичній, що виходить від [45,0] в азимуті 90. Наступна функція виконує трюк (використовуючи метод Ньютона):

function [lat2,lon2] = semiconj(lat1, lon1, azi1)
% Find the point where neighboring parallel geodesics emanating from
% close to [lat1, lon1] with azimuth azi1 intersect.

  % First guess is 90 deg on aux sphere
  [lat2, lon2, ~, ~, m12, M12, M21, s12] = ...
      geodreckon(lat1, lon1, 90, azi1, defaultellipsoid(), true);
  M12
  % dM12/ds2 = - (1 - M12*M21/m12)
  for i = 1:3
    s12 = s12 - M12 / ( -(1 - M12*M21)/m12 ); % Newton
    [lat2, lon2, ~, ~, m12, M12, M21] = geodreckon(lat1, lon1, s12, azi1);
    M12
  end
end

Наприклад, це дає:

[lat2, lon2] = semiconj (45, 0, 90)
М12 = 0,00262997817649321
М12 = -6.08402492665097е-09
М12 = 4.38017677684144e-17
М12 = 4.38017677684144e-17
lat2 = -0.106909087322479
lon2 = 89,8929107279317

+1. Однак незрозуміло, що загальний кореневий пошук буде працювати не менш добре: функція чудово поводиться поблизу свого оптимального, а метод Ньютона, наприклад, також буде сходитися квадратично. ( Mathematica, як правило, робить приблизно чотири кроки для сходження; для кожного кроку потрібно чотири оцінки, щоб оцінити якобіан.) Справжня перевага, яке я бачу для вашого методу, полягає в тому, що він може бути легко скриптований в ГІС, не вдаючись до використання кореневого пошуку.
whuber

Я згоден. Мій метод еквівалентний Ньютону, тому, на відміну від кореневого методу Mathematica, немає необхідності оцінювати градієнт, беручи різниці.
cffk

Правильно - але ви повинні робити перепроектування кожен раз, схоже, це приблизно однакова робота. Я вдячний простоті та вишуканості вашого підходу, однак: відразу очевидно, що він повинен працювати і швидко сходиться.
whuber

Я опублікував результати для тих же тестових балів у своїй відповіді.
Кірк Куйкендалл

3

Мені було цікаво побачити, наскільки швидко підхід @ cffk конвергується на рішення, тому я написав тест, використовуючи arcobjects, який дав цей вихід. Відстані в метрах:

0 longitude: 0 latitude: 90
    Distances: 3134.05443974188 2844.67237777542 3234.33025754997
    Diffs: 289.382061966458 -389.657879774548 -100.27581780809
1 longitude: 106.906152157596 latitude: -6.31307123035178
    Distances: 1450.23208989615 1450.23208089398 1450.23209429293
    Diffs: 9.00216559784894E-06 -1.33989510686661E-05 -4.39678547081712E-06
2 longitude: 106.906583669013 latitude: -6.29691590176649
    Distances: 1450.23206976414 1450.23206976408 1450.23206976433
    Diffs: 6.18456397205591E-11 -2.47382558882236E-10 -1.85536919161677E-10
3 longitude: 106.906583669041 latitude: -6.29691590154641
    Distances: 1450.23206976438 1450.23206976423 1450.23206976459
    Diffs: 1.47565515362658E-10 -3.61751517630182E-10 -2.14186002267525E-10
4 longitude: 106.906583669041 latitude: -6.29691590154641
    Distances: 1450.23206976438 1450.23206976423 1450.23206976459
    Diffs: 1.47565515362658E-10 -3.61751517630182E-10 -2.14186002267525E-10

Ось вихідний код. (Редагувати) Змінено FindCircleCenter для обробки перетинів (центральних точок), які падають від краю азимутальної проекції:

public static void Test()
{
    var t = Type.GetTypeFromProgID("esriGeometry.SpatialReferenceEnvironment");
    var srf = Activator.CreateInstance(t) as ISpatialReferenceFactory2;
    var pcs = srf.CreateProjectedCoordinateSystem((int)esriSRProjCSType.esriSRProjCS_WGS1984N_PoleAziEqui)
        as IProjectedCoordinateSystem2;

    var pntA = MakePoint(106.9004975375, -6.28530175, pcs.GeographicCoordinateSystem);
    var pntB = MakePoint(106.89573839, -6.28955287, pcs.GeographicCoordinateSystem);
    var pntC = MakePoint(106.908087643421, -6.28388865789474, pcs.GeographicCoordinateSystem);

    int maxIter = 5;
    for (int i = 0; i < maxIter; i++)
    {
        var msg = string.Format("{0} longitude: {1} latitude: {2}", i, pcs.get_CentralMeridian(true), pcs.LatitudeOfOrigin);
        Debug.Print(msg);
        var newCenter = FindCircleCenter(ProjectClone(pntA, pcs), ProjectClone(pntB, pcs), ProjectClone(pntC, pcs));
        newCenter.Project(pcs.GeographicCoordinateSystem); // unproject
        var distA = GetGeodesicDistance(newCenter, pntA);
        var distB = GetGeodesicDistance(newCenter, pntB);
        var distC = GetGeodesicDistance(newCenter, pntC);
        Debug.Print("\tDistances: {0} {1} {2}", distA, distB, distC);
        var diffAB = distA - distB;
        var diffBC = distB - distC;
        var diffAC = distA - distC;
        Debug.Print("\tDiffs: {0} {1} {2}", diffAB, diffBC, diffAC);

        pcs.set_CentralMeridian(true, newCenter.X);
        pcs.LatitudeOfOrigin = newCenter.Y;
    }
}
public static IPoint FindCircleCenter(IPoint a, IPoint b, IPoint c)
{
    // from http://blog.csharphelper.com/2011/11/08/draw-a-circle-through-three-points-in-c.aspx
    // Get the perpendicular bisector of (x1, y1) and (x2, y2).
    var x1 = (b.X + a.X) / 2;
    var y1 = (b.Y + a.Y) / 2;
    var dy1 = b.X - a.X;
    var dx1 = -(b.Y - a.Y);

    // Get the perpendicular bisector of (x2, y2) and (x3, y3).
    var x2 = (c.X + b.X) / 2;
    var y2 = (c.Y + b.Y) / 2;
    var dy2 = c.X - b.X;
    var dx2 = -(c.Y - b.Y);

    // See where the lines intersect.
    var cx = (y1 * dx1 * dx2 + x2 * dx1 * dy2 - x1 * dy1 * dx2 - y2 * dx1 * dx2)
        / (dx1 * dy2 - dy1 * dx2);
    var cy = (cx - x1) * dy1 / dx1 + y1;

    // make sure the intersection point falls
    // within the projection.
    var earthRadius = ((IProjectedCoordinateSystem)a.SpatialReference).GeographicCoordinateSystem.Datum.Spheroid.SemiMinorAxis;

    // distance is from center of projection
    var dist = Math.Sqrt((cx * cx) + (cy * cy));
    double factor = 1.0;
    if (dist > earthRadius * Math.PI)
    {
        // apply a factor so we don't fall off the edge
        // of the projection
        factor = earthRadius / dist;
    }
    var outPoint = new PointClass() as IPoint;
    outPoint.PutCoords(cx * factor, cy* factor);
    outPoint.SpatialReference = a.SpatialReference;
    return outPoint;
}

public static double GetGeodesicDistance(IPoint pnt1, IPoint pnt2)
{
    var pc = new PolylineClass() as IPointCollection;
    var gcs = pnt1.SpatialReference as IGeographicCoordinateSystem;
    if (gcs == null)
        throw new Exception("point does not have a gcs");
    ((IGeometry)pc).SpatialReference = gcs;
    pc.AddPoint(pnt1);
    pc.AddPoint(pnt2);
    var t = Type.GetTypeFromProgID("esriGeometry.SpatialReferenceEnvironment");
    var srf = Activator.CreateInstance(t) as ISpatialReferenceFactory2;
    var unit = srf.CreateUnit((int)esriSRUnitType.esriSRUnit_Meter) as ILinearUnit;
    var pcGeodetic = pc as IPolycurveGeodetic;
    return pcGeodetic.get_LengthGeodetic(esriGeodeticType.esriGeodeticTypeGeodesic, unit);
}

public static IPoint ProjectClone(IPoint pnt, ISpatialReference sr)
{
    var clone = ((IClone)pnt).Clone() as IPoint;
    clone.Project(sr);
    return clone;
}

public static IPoint MakePoint(double longitude, double latitude, ISpatialReference sr)
{
    var pnt = new PointClass() as IPoint;
    pnt.PutCoords(longitude, latitude);
    pnt.SpatialReference = sr;
    return pnt;
}

Також у червні 2013 року існує альтернативний підхід випуску журналу MSDN, Оптимізація методів Amoeba за допомогою C # .


Редагувати

Раніше розміщений код в деяких випадках конвергувався в антипод. Я змінив код, щоб він дав цей вихід для тестових точок @ cffk.

Ось результат, який він зараз виробляє:

0 0
0 longitude: 0 latitude: 0
    MaxDiff: 1859074.90170379 Distances: 13541157.6493561 11682082.7476523 11863320.2116807
1 longitude: 43.5318402621384 latitude: -17.1167429904981
    MaxDiff: 21796.9793742411 Distances: 12584188.7592282 12588146.4851222 12566349.505748
2 longitude: 32.8331167578493 latitude: -16.2707976739314
    MaxDiff: 6.05585224926472 Distances: 12577536.3369782 12577541.3560203 12577542.3928305
3 longitude: 32.8623898057665 latitude: -16.1374156408507
    MaxDiff: 5.58793544769287E-07 Distances: 12577539.6118671 12577539.6118666 12577539.6118669
4 longitude: -147.137582018133 latitude: 16.1374288796667
    MaxDiff: 1.12284109462053 Distances: 7441375.08265703 7441376.12671342 7441376.20549812
5 longitude: -147.157742373074 latitude: 16.2233413614432
    MaxDiff: 7.45058059692383E-09 Distances: 7441375.70752843 7441375.70752842 7441375.70752842
5 longitude: -147.157742373074 latitude: 16.2233413614432 Distance 7441375.70752843
iterations: 5

Ось переглянений код:

class Program
{
    private static LicenseInitializer m_AOLicenseInitializer = new tripoint.LicenseInitializer();

    [STAThread()]
    static void Main(string[] args)
    {
        //ESRI License Initializer generated code.
        m_AOLicenseInitializer.InitializeApplication(new esriLicenseProductCode[] { esriLicenseProductCode.esriLicenseProductCodeStandard },
        new esriLicenseExtensionCode[] { });
        try
        {
            var t = Type.GetTypeFromProgID("esriGeometry.SpatialReferenceEnvironment");
            var srf = Activator.CreateInstance(t) as ISpatialReferenceFactory2;
            var pcs = srf.CreateProjectedCoordinateSystem((int)esriSRProjCSType.esriSRProjCS_World_AzimuthalEquidistant)
                as IProjectedCoordinateSystem2;
            Debug.Print("{0} {1}", pcs.get_CentralMeridian(true), pcs.LatitudeOfOrigin);
            int max = int.MinValue;
            for (int i = 0; i < 1; i++)
            {
                var iterations = Test(pcs);
                max = Math.Max(max, iterations);
                Debug.Print("iterations: {0}", iterations);
            }
            Debug.Print("max number of iterations: {0}", max);
        }
        catch (Exception ex)
        {
            Debug.Print(ex.Message);
            Debug.Print(ex.StackTrace);
        }
        //ESRI License Initializer generated code.
        //Do not make any call to ArcObjects after ShutDownApplication()
        m_AOLicenseInitializer.ShutdownApplication();
    }
    public static int Test(IProjectedCoordinateSystem2 pcs)
    {
        var pntA = MakePoint(-74.0, 41.0, pcs.GeographicCoordinateSystem);
        var pntB = MakePoint(140.0, 36.0, pcs.GeographicCoordinateSystem);
        var pntC = MakePoint(175.0, -41.0, pcs.GeographicCoordinateSystem);


        //var r = new Random();
        //var pntA = MakeRandomPoint(r, pcs.GeographicCoordinateSystem);
        //var pntB = MakeRandomPoint(r, pcs.GeographicCoordinateSystem);
        //var pntC = MakeRandomPoint(r, pcs.GeographicCoordinateSystem);

        int maxIterations = 100;
        for (int i = 0; i < maxIterations; i++)
        {
            var msg = string.Format("{0} longitude: {1} latitude: {2}", i, pcs.get_CentralMeridian(true), pcs.LatitudeOfOrigin);
            Debug.Print(msg);
            var newCenter = FindCircleCenter(ProjectClone(pntA, pcs), ProjectClone(pntB, pcs), ProjectClone(pntC, pcs));
            var c = ((IClone)newCenter).Clone() as IPoint;
            newCenter.Project(pcs.GeographicCoordinateSystem); // unproject
            //newCenter = MakePoint(-147.1577482, 16.2233394, pcs.GeographicCoordinateSystem);
            var distA = GetGeodesicDistance(newCenter, pntA);
            var distB = GetGeodesicDistance(newCenter, pntB);
            var distC = GetGeodesicDistance(newCenter, pntC);
            var diffAB = Math.Abs(distA - distB);
            var diffBC = Math.Abs(distB - distC);
            var diffAC = Math.Abs(distA - distC);
            var maxDiff = GetMax(new double[] {diffAB,diffAC,diffBC});
            Debug.Print("\tMaxDiff: {0} Distances: {1} {2} {3}",maxDiff, distA, distB, distC);
            if (maxDiff < 0.000001)
            {
                var earthRadius = pcs.GeographicCoordinateSystem.Datum.Spheroid.SemiMinorAxis;
                if (distA > earthRadius * Math.PI / 2.0)
                {
                    newCenter = AntiPode(newCenter);
                }
                else
                {
                    Debug.Print("{0} longitude: {1} latitude: {2} Distance {3}", i, pcs.get_CentralMeridian(true), pcs.LatitudeOfOrigin, distA);
                    return i;
                }
            }
            //Debug.Print("\tDiffs: {0} {1} {2}", diffAB, diffBC, diffAC);

            pcs.set_CentralMeridian(true, newCenter.X);
            pcs.LatitudeOfOrigin = newCenter.Y;
        }
        return maxIterations;
    }

    public static IPoint FindCircleCenter(IPoint a, IPoint b, IPoint c)
    {
        // from http://blog.csharphelper.com/2011/11/08/draw-a-circle-through-three-points-in-c.aspx
        // Get the perpendicular bisector of (x1, y1) and (x2, y2).
        var x1 = (b.X + a.X) / 2;
        var y1 = (b.Y + a.Y) / 2;
        var dy1 = b.X - a.X;
        var dx1 = -(b.Y - a.Y);

        // Get the perpendicular bisector of (x2, y2) and (x3, y3).
        var x2 = (c.X + b.X) / 2;
        var y2 = (c.Y + b.Y) / 2;
        var dy2 = c.X - b.X;
        var dx2 = -(c.Y - b.Y);

        // See where the lines intersect.
        var cx = (y1 * dx1 * dx2 + x2 * dx1 * dy2 - x1 * dy1 * dx2 - y2 * dx1 * dx2)
            / (dx1 * dy2 - dy1 * dx2);
        var cy = (cx - x1) * dy1 / dx1 + y1;

        // make sure the intersection point falls
        // within the projection.
        var earthRadius = ((IProjectedCoordinateSystem)a.SpatialReference).GeographicCoordinateSystem.Datum.Spheroid.SemiMinorAxis;

        // distance is from center of projection
        var dist = Math.Sqrt((cx * cx) + (cy * cy));
        double factor = 1.0;
        if (dist > earthRadius * Math.PI)
        {
            // apply a factor so we don't fall off the edge
            // of the projection
            factor = earthRadius / dist;
        }
        var outPoint = new PointClass() as IPoint;
        outPoint.PutCoords(cx * factor, cy* factor);
        outPoint.SpatialReference = a.SpatialReference;
        return outPoint;
    }

    public static IPoint AntiPode(IPoint pnt)
    {
        if (!(pnt.SpatialReference is IGeographicCoordinateSystem))
            throw new Exception("antipode of non-gcs projection not supported");
        var outPnt = new PointClass() as IPoint;
        outPnt.SpatialReference = pnt.SpatialReference;
        if (pnt.X < 0.0)
            outPnt.X = 180.0 + pnt.X;
        else
            outPnt.X = pnt.X - 180.0;
        outPnt.Y = -pnt.Y;
        return outPnt;
    }

    public static IPoint MakeRandomPoint(Random r, IGeographicCoordinateSystem gcs)
    {
        var latitude = (r.NextDouble() - 0.5) * 180.0;
        var longitude = (r.NextDouble() - 0.5) * 360.0;
        //Debug.Print("{0} {1}", latitude, longitude);
        return MakePoint(longitude, latitude, gcs);
    }
    public static double GetMax(double[] dbls)
    {
        var max = double.MinValue;
        foreach (var d in dbls)
        {
            if (d > max)
                max = d;
        }
        return max;
    }
    public static IPoint MakePoint(IPoint[] pnts)
    {
        double sumx = 0.0;
        double sumy = 0.0;
        foreach (var pnt in pnts)
        {
            sumx += pnt.X;
            sumy += pnt.Y;
        }
        return MakePoint(sumx / pnts.Length, sumy / pnts.Length, pnts[0].SpatialReference);
    }
    public static double GetGeodesicDistance(IPoint pnt1, IPoint pnt2)
    {
        var pc = new PolylineClass() as IPointCollection;
        var gcs = pnt1.SpatialReference as IGeographicCoordinateSystem;
        if (gcs == null)
            throw new Exception("point does not have a gcs");
        ((IGeometry)pc).SpatialReference = gcs;
        pc.AddPoint(pnt1);
        pc.AddPoint(pnt2);
        var t = Type.GetTypeFromProgID("esriGeometry.SpatialReferenceEnvironment");
        var srf = Activator.CreateInstance(t) as ISpatialReferenceFactory2;
        var unit = srf.CreateUnit((int)esriSRUnitType.esriSRUnit_Meter) as ILinearUnit;
        var pcGeodetic = pc as IPolycurveGeodetic;
        return pcGeodetic.get_LengthGeodetic(esriGeodeticType.esriGeodeticTypeGeodesic, unit);
    }

    public static IPoint ProjectClone(IPoint pnt, ISpatialReference sr)
    {
        var clone = ((IClone)pnt).Clone() as IPoint;
        clone.Project(sr);
        return clone;
    }

    public static IPoint MakePoint(double longitude, double latitude, ISpatialReference sr)
    {
        var pnt = new PointClass() as IPoint;
        pnt.PutCoords(longitude, latitude);
        pnt.SpatialReference = sr;
        return pnt;
    }
}

Редагувати

Ось результати, які я отримую з esriSRProjCS_WGS1984N_PoleAziEqui

0 90
0 longitude: 0 latitude: 90
    MaxDiff: 1275775.91880553 Distances: 8003451.67666723 7797996.2370572 6727675.7578617
1 longitude: -148.003774863594 latitude: 9.20238223616225
    MaxDiff: 14487.6784785809 Distances: 7439006.46128994 7432752.45732905 7447240.13580763
2 longitude: -147.197808459106 latitude: 16.3073233548167
    MaxDiff: 2.32572609744966 Distances: 7441374.94409209 7441377.26981819 7441375.90768183
3 longitude: -147.157734641831 latitude: 16.2233338760411
    MaxDiff: 7.72997736930847E-08 Distances: 7441375.70752842 7441375.70752848 7441375.7075284
3 longitude: -147.157734641831 latitude: 16.2233338760411 Distance 7441375.70752842

Це вражаюче швидке зближення! (+1)
whuber

Ви повинні використовувати чесну доброту азимутальну рівновіддалену проекцію, зосереджену на newCenter. Натомість ви використовуєте проекцію, зосереджену на N полюсі, і зміщуєте походження на newCenter. Тому може бути випадково, що ви отримаєте гідне рішення в цьому випадку (можливо, тому, що точки близько один від одного?). Добре було б спробувати це з 3 очками тисячі км. Реалізація азимутальної еквідистантної проекції наведена в mathworks.com/matlabcentral/fileexchange/…
cffk

@cffk Єдиний інший спосіб, який я бачу створити азимутальну рівновіддалену проекцію, зосереджену на конкретній точці, - використовувати той самий метод, але з esriSRProjCS_World_AzimuthalEquidistant замість esriSRProjCS_WGS1984N_PoleAziEqui (або esriSRProjCS_WGS19WSS19) Єдина відмінність полягає в тому, що він зосереджений на 0,0 замість 0,90 (або 0, -90). Чи можете ви вказувати мені на тест з математикою, щоб побачити, чи це дає різні результати від прогнозу "чесного доброго"?
Кірк Куйкендалл

@KirkKuykendall: див. Додаток до моєї першої відповіді.
cffk

1
@KirkKuykendall Тож, можливо, ESRI реалізував прогноз "чесного доброго"? Основна властивість, необхідна для роботи цього алгоритму, полягає в тому, що відстані, виміряні від "центральної точки", є істинними. І цю властивість досить просто перевірити. (Властивість азимуталу щодо центральної точки є вторинною і може впливати лише на швидкість конвергенції.)
cffk
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.