Как я могу использовать ArcGIS 10.1, чтобы найти геодезическую равноудаленную точку, определяемую тремя точками?

12

Например, у меня есть координаты для трех базовых точек на береговой линии, и мне нужно найти координаты точки у берега, которая равноудалена от всех трех. Это простое упражнение по геометрии, но все измерения должны учитывать геодезию.

Если бы я подходил к этому евклидовым образом, я мог бы измерить геодезические пути, соединяющие базовые точки, найти середины сторон полученного треугольника и создать перпендикулярные ортодромы для каждого из этих путей. Предполагается, что три локсодрома сходятся в равноудаленной точке. Если это правильный метод, должен быть более простой способ сделать это в Arc.

Мне нужно найти O

смазка
источник
Есть ли ограничения на относительное положение 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 (в виде лат-длинных пар) на эллипсоиде найдите точку 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, lambda). В этой настройке функция

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

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

Найти все возможные (фи, лямбда), для которых F (фи, лямбда) = (0,0).

Вот где абстракция окупается: существует множество отличного программного обеспечения для решения этой (чисто численной многомерной задачи поиска корней). Это работает так, что вы пишете подпрограмму для вычисления F , а затем передаете ее программному обеспечению вместе с любой информацией об ограничениях на ее вход ( phi должно лежать в диапазоне от -90 до 90 градусов, а лямбда должна лежать в диапазоне от -180 до 180 градусов). Он запускается за доли секунды и возвращает (обычно) только одно значение ( фи , лямбда ), если он может найти его.

Есть детали для обработки, потому что в этом есть искусство: есть разные методы решения на выбор, в зависимости от того, как F «ведет себя»; это помогает «управлять» программным обеспечением, предоставляя ему разумную отправную точку для его поиска (это один из способов получить ближайшее решение, а не любое другое); и вам обычно нужно указать, насколько точным вы хотите, чтобы решение было (чтобы он знал, когда прекратить поиск). (Для получения дополнительной информации о том, что аналитики ГИС должны знать о таких деталях, которые часто встречаются в проблемах ГИС, посетите разделы «Рекомендации», которые необходимо включить в курс «Компьютерные науки для геопространственных технологий») и посмотрите в конце раздела «Разное». )


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

Анализ показывает нам нужно запрограммировать две вещи: неочищенную первоначальную оценку решения и вычисление 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 и почти все другие ГИС - содержит код для вычисления точных расстояний на эллипсоиде, писать практически нечего. Мы просто вызываем процедуру поиска корня:

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


пример

Чтобы протестировать эту реализацию и лучше понять, как ведет себя проблема, вот контурный график среднеквадратичного расхождения расстояний для трех широко расположенных базовых точек. (Среднеквадратичное расхождение получается путем вычисления всех трех разностей 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, а светлые области показывают высокие значения.

Эта карта ясно показывает, что рядом находится другое решение (-49.2018206, 130.0297177) (вычислено для среднеквадратичного значения в два нанометра путем установки начального значения поиска, диаметрально противоположного первому решению.)


Ловушки

Численная нестабильность

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

Например, начиная с базовых точек в (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, показанный здесь (прямо), и все будет готово.

Whuber
источник
3
+1 Очень тщательно. Я думаю, вам, возможно, придется начать взимать плату за это, @whuber.
Радар
2
@ Радар Спасибо. Я надеюсь, что люди купят мою книгу, когда (когда-нибудь) она когда-нибудь появится :-).
whuber
1
Будет ли Билл ... Отправить проект !!!
Отлично! Тем не менее, похоже, что аналитическое решение было бы возможно. Повторяя задачу в трехмерном декартовом пространстве с 3 точками A, B, C и E, где E - центр земли. Затем найдите две плоскости Plane1 и Plane2. Plane1 будет плоскостью, которая является нормальной к плоскости ABE и проходит через E, среднюю точку (A, B). Аналогичным образом, Plane2 будет плоскостью, перпендикулярной плоскости A, и проходящей через E, среднюю точку (C, E). Линия O, образованная пересечением Плоскости1 и Плоскости2, представляет точки, равноудаленные от 3 точек. Чем ближе две точки к A (или B или C), где линия O пересекает сферу, это точка O.
Кирк Кайкендалл
Это аналитическое решение @Kirk применимо только к сфере. (Пересечения плоскостей с эллипсоидом никогда не являются перпендикулярными биссектрисами в метрике эллипсоида, за исключением нескольких очевидных исключительных случаев: когда они являются меридианами или экватором.)
whuber
3

Как вы заметили, эта проблема возникает при определении морских границ; ее часто называют «трехточечной» проблемой, и вы можете найти ее в Google и найти несколько статей, посвященных ее решению. Одна из этих статей написана мной (!), И я предлагаю точное и быстро сходящееся решение. См. Раздел 14 http://arxiv.org/abs/1102.1215.

Метод состоит из следующих шагов:

  1. угадать три точки O
  2. используйте O в качестве центра азимутальной эквидистантной проекции
  3. проект A, B, C, к этой проекции
  4. найти три-точку в этой проекции, O '
  5. используйте O 'как новый центр проекции
  6. повторять до тех пор, пока O 'и O не совпадут

Необходимая формула для трехточечного решения в проекции приведена в статье. Пока вы используете точную азимутальную равноудаленную проекцию, ответ будет точным. Сходимость является квадратичной, что означает, что требуется всего несколько итераций. Это почти наверняка превзойдет общие методы поиска корней, предложенные @whuber.

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


редактировать

Проблема нахождения три-точки в вырожденном случае @uuber (45 + eps, 0) (45,0) (45-eps, 0) была рассмотрена Кэли в статье « О геодезических линиях на сплюснутом сфероиде» , Phil. Магнето (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 км больше, чем оценка Whuber (которая показывает, насколько нестабильным является этот случай). Для сферической земли трипункт будет, конечно, (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] = tripoint (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)
M12 = 0,00262997817649321
M12 = -6.08402492665097e-09
М12 = 4,38017677684144е-17
М12 = 4,38017677684144е-17
lat2 = -0.106909087322479
lon2 = 89,8929107279317
cffk
источник
+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;
}

Есть также альтернативный подход в выпуске журнала MSDN Magazine в июне 2013 года « Оптимизация метода 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. Вместо этого вы используете проекцию, центрированную на северном полюсе, и перемещаете начало координат на новый центр. Поэтому может быть случайным, что вы получите достойное решение в этом случае (возможно, потому что точки находятся близко друг к другу?). Было бы неплохо попробовать его с разницей в 3 тысячи километров. Реализация азимутальной эквидистантной проекции приведена в mathworks.com/matlabcentral/fileexchange/…
cffk
@cffk Единственный другой способ создания азимутальной эквидистантной проекции с центром в конкретной точке - использовать тот же метод, но с esriSRProjCS_World_AzimuthalEquidistant вместо esriSRProjCS_WGS1984N_PoleAziEqui (или esriSRProjCS_WGS1984iqui). Единственное отличие состоит в том, что он центрирован на 0,0 вместо 0,90 (или 0, -90). Можете ли вы помочь мне в проведении теста с математическими работами, чтобы увидеть, дает ли он результаты, отличные от прогноза "честность к добру"?
Кирк Кайкендалл,
@KirkKuykendall: см. Приложение к моему первому ответу.
cffk
1
@KirkKuykendall Так, может быть, ESRI реализовал проекцию "честность к добру"? Ключевое свойство, необходимое для работы этого алгоритма, состоит в том, что расстояния, измеренные от «центральной точки», являются истинными. И это свойство достаточно легко проверить. (Азимутальное свойство относительно центральной точки является вторичным и может влиять только на скорость сходимости.)
cffk