ArcGIS 10.1を使用して、3点で定義された測地線の等距離点を見つけるにはどうすればよいですか?


12

たとえば、海岸線上の3つのベースポイントの座標があり、3つすべてから等距離にある海岸沖のポイントの座標を見つける必要があります。これはジオメトリの簡単な練習ですが、すべての測定では測地学を考慮する必要があります。

ユークリッドの方法でこれに近づいていた場合、基点を接続する測地線の経路を測定し、結果の三角形の辺の中点を見つけ、それらの経路のそれぞれに垂直な正三角形を作成できました。3つのロキソドロームはおそらく等距離点に収束するでしょう。これが正しい方法である場合、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原則として、悪いケースはなく、数値的に不安定なケースしかありません。これらは、3つの基点が同一直線上にあるときに発生します。2つの解(球面モデル)は、共通測地線の2つの極で発生します。楕円モデルでは、これらの極が予想される場所の近くで発生します。
whuber

ここでのロキソドロームの使用は正しくありません。それらは垂直二等分線ではありません。球体では、これらの線は大圏(測地線)の一部になりますが、楕円体では、測地線からわずかに逸脱します。
whuber

回答:


10

この答えは複数のセクションに分かれています。

  • 問題の分析と削減。「缶詰」ルーチンで目的のポイントを見つける方法を示します。

  • 図:作業用プロトタイプ、作業コードを提供します。

  • 、ソリューションの例を示します。

  • 潜在的な問題とそれらに対処する方法を議論する落とし穴

  • ArcGISの実装、カスタムArcGISツールの作成に関するコメント、および必要なルーチンの入手先。


問題の分析と削減

(完全な円形の)球体モデルには常に解が存在することを確認することから始めましょう。実際には、正確に2つの解があります。基点A、B、およびCが与えられると、各ペアは2つの与えられた点から等距離にある点のセットである「垂直二等分線」を決定します。この二等分線は測地線(大円)です。球体のジオメトリは楕円形です。2つの測地線は(2つの一意のポイントで)交差します。したがって、ABの二等分線とBCの二等分線の交点は、定義上、A、B、およびCから等距離であるため、問題を解決します。(下の最初の図を参照してください。)

楕円体では物事はより複雑に見えますが、球体の小さな摂動であるため、同様の動作が期待できます。(これを分析すると、遠すぎます。)楕円体の正確な距離を計算するために(GISの内部で)使用される複雑な数式は、概念的な複雑さではありませんが、問題は基本的に同じです。 問題が実際にどれほど単純であるかを見るために、多少抽象的に述べましょう。このステートメントの「d(U、V)」は、ポイントUとVの間の完全に正確な距離を指します。

楕円体上の3つの点A、B、C(緯度経度ペアとして)が与えられた場合、(1)d(X、A)= d(X、B)= d(X、C)および( 2)この共通距離は可能な限り小さい。

これら3つの距離はすべて、未知のXに依存します。このような違い距離のU(X)= D(X、A) - D(X、B)とV(X)= D(X、B) - D(X、C)は、Xの実数値関数であります繰り返しますが、やや抽象的には、これらの違いを順序付けられたペアにまとめることができます。また、Xの座標として(lat、lon)を使用します。これにより、X =(phi、lambda)のように、それを順序付きペアと見なすこともできます。この設定では、関数

F(phi、lambda)=(u(X)、v(X))

は、2次元空間の一部からの関数であり、2次元空間の値を取得し、問題は

F(phi、lambda)=(0,0)の可能なすべての(phi、lambda)を見つけます。

ここに抽象化が功を奏します。 この(純粋に数値的な多次元のルート探索)問題を解決するための優れたソフトウェアがたくさんあります。 動作方法は、Fを計算するルーチンを作成し、その入力の制限に関する情報とともにソフトウェアに渡すことです(phiは-90〜90度、lambdaは-180〜180でなければなりません。度)。ほんの数秒でクランクを切り、(通常)1つの値(philambda)を返します(見つかった場合)。

これには技術があるため、処理する詳細があります。Fの「動作」に応じて、さまざまな解決方法を選択できます。検索の合理的な出発点を与えることにより、ソフトウェアを「操縦」するのに役立ちます(これは、他のソリューションではなく、最も近いソリューションを取得できる1つの方法です)。通常、ソリューションの精度を指定する必要があります(そのため、いつ検索を停止するかがわかります)。(GISアナリストがGIS問題でよく出てくるこのような詳細について知っておくべきことの詳細については、地理空間テクノロジーのためのコンピューターサイエンスコースに含まれる推奨トピックをご覧ください。 )


イラスト:実用プロトタイプ

この分析では、解の大まかな初期推定とF自体の計算という2つのことをプログラムする必要があることが示されています。

最初の推定は、3つのベースポイントの「球面平均」によって行うことができます。これは、地心デカルト(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座標で、2番目はy座標で、ベクトルによって作られた角度を返します( x、y)。)

2番目の部分に関しては、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
   ];

この実装の最も注目すべき点は、常にそれぞれ180度と360度を法として計算することにより、緯度(f)と経度(q)を制約する必要性をどのように回避するかです。これにより、問題を制約する必要がなくなります(多くの場合、問題が発生します)。制御パラメーターMaxIterationsなどを調整して、このコードが可能な限り最高の精度を提供するようにします。

動作を確認するために、関連する質問で指定された3つのベースポイントに適用してみましょう。

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

{-6.29692、106.907}

この解と3つの点の間の計算された距離は次のとおりです。

{1450.23206979、1450.23206979、1450.23206978}

(これらはメートルです)。これらは、有効桁数11桁目で一致します(実際には、距離が1ミリ程度を超えるほど正確ではないため、実際には精度が高すぎます)。これらの3つのポイント(黒)、3つの相互二等分線、および解(赤)の写真を次に示します。

図1


この実装をテストし、問題の振る舞いをよりよく理解するために、3つの間隔の広いベースポイントの距離の二乗平均平方根の不一致の等高線図を次に示します。(RMSの不一致は、3つの差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)は、このPlate Carree投影では赤で示されています。ソリューション(49.2644488、-49.9052992)-計算に0.03秒かかりました-は黄色です。関連するすべての距離は数千キロメートルにも関わらず、RMSの不一致は3 ナノメートル未満です。暗い領域はRMSの小さな値を示し、明るい領域は高い値を示します。

このマップは、別のソリューションが(-49.2018206、130.0297177)の近くにあることを明確に示しています(最初のソリューションの正反対に初期検索値を設定することで2ナノメートルの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メートルです。彼らは最も近いミリメートルに同意します!この結果がどれほど正確であるかはわかりませんが、他の実装がこれから遠く離れた場所を返し、3つの距離のほぼ同等の同等性を示しても、驚くことではありません。

計算時間

これらの計算は、複雑な距離計算を使用したかなりの検索を伴うため、高速ではなく、通常はわずかな秒を必要とします。リアルタイムアプリケーションはこれに注意する必要があります。


ArcGISの実装

Pythonは、ArcGISの推奨スクリプト環境です(バージョン9以降)。scipy.optimizeパッケージは、多変量rootfinder持ってroot何をすべきFindRootではないMathematicaのコードを。もちろん、ArcGIS自体が正確な楕円距離計算を提供します。残りはすべて実装の詳細です:基点座標の取得方法(ユーザーが入力したレイヤーから、テキストファイルから、マウスから)、出力の表示方法(座標として)を決定します画面に表示されますか?グラフィックポイントとして?レイヤーの新しいポイントオブジェクトとして?)、そのインターフェースを記述し、ここに示されているMathematicaコードを移植します(直接)、あなたはすべて設定されます。


3
+1非常に徹底。@whuber、これに対する課金を開始する必要があると思います。
レーダー

2
@レーダーありがとう。私は、本が最終的に登場するとき(-)に人々が私の本を買うことを望んでいます:-)。
whuber

1
ビルをします...下書きを送ってください!!!

優秀な!それでも、分析的な解決策は可能だと思われます。問題を3点A、B、C、Eの3次元デカルト空間に言い換えることにより、Eは地球の中心です。次に、2つの平面Plane1とPlane2を見つけます。Plane1は、planeABEに垂直で、E、midpoint(A、B)を通過する平面です。同様に、Plane2はplaneACEに垂直な平面で、E、midpoint(C、E)を通過します。Plane1とPlane2の交差によって形成されるlineOは、3つのポイントから等距離のポイントを表します。lineOがsphereと交差するA(またはBまたはC)に近い2つのポイントはpointOです。
カーククイケンドール

この分析ソリューション@Kirkは、球体にのみ適用されます。(楕円体と平面の交点は、垂直二等分線は決してありません楕円のメトリックでは、いくつかの明白な例外的なケースを除いて:彼らは、経絡や赤道あるとき。)
whuber

3

ご指摘のとおり、この問題は海上境界を決定する際に発生します。多くの場合、「トライポイント」問題と呼ばれ、これをグーグルで検索して、それに対処するいくつかの論文を見つけることができます。これらの論文の1つは私です(!)。私は正確かつ迅速に収束するソリューションを提供します。http://arxiv.org/abs/1102.1215のセクション14を参照してください

この方法は、次の手順で構成されます。

  1. トライポイントOを推測する
  2. 方位角の等距離射影の中心としてOを使用します
  3. プロジェクトA、B、C、この投影
  4. この投影の3点を見つける、O '
  5. O 'を新しい投影中心として使用
  6. O 'とOが一致するまで繰り返す

投影法の3点解法に必要な式は、論文に記載されています。正確な方位角の等距離投影法を使用している限り、答えは正確です。収束は二次的であり、数回の反復のみが必要です。これは、ほぼ確実に、@ whuberによって提案された一般的なルート検索方法よりも優れています。

ArcGISを直接サポートすることはできません。https://pypi.python.org/pypi/geographiclibから測地線計算を行うためにpythonパッケージを取得し、 これに基づいて投影をコーディングするのは簡単です。


編集

@whuberの縮退ケース(45 + eps、0)(45,0)(45-eps、0)で3点を見つける問題は、Cayley の扁平回転楕円体上の測地線、Phil。マグ (1870)、 http://books.google.com/books?id = 4XGIOoCMYYAC&pg = PA15

この場合、3点は(45,0)からの測地線を方位角90で追跡し、測地線スケールが消失する点を見つけることによって取得されます。WGS84楕円体の場合、このポイントは(-0.10690908732248、89.89291072793167)です。この点から(45.001,0)、(45,0)、(44.999)のそれぞれまでの距離は、10010287.665788943 m(ナノメートル以内)です。これは、whuberの推定値よりも約1882 km長くなっています(このケースがどれだけ不安定であるかを示しています)。球状の地球の場合、3点はもちろん(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>長い形式
octave: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]などの隣接する共線点では不正確です。その場合、方位角90の[45,0]から発する測地線上でM 12 = 0を解く必要があります。次の関数がトリックを実行します(ニュートン法を使用)。

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
M12 = 4.38017677684144e-17
M12 = 4.38017677684144e-17
lat2 = -0.106909087322479
lon2 = 89.8929107279317

+1。ただし、一般的なルートファインダーのパフォーマンスが低下するかどうかは不明です。関数は最適に近い動作をし、たとえばニュートン法も2次的に収束します。(Mathematicaは通常収束するのに約4つのステップを取ります。各ステップはヤコビアンを推定するために4つの評価を必要とします。)私があなたの方法に見る本当の利点は、ルートファインダーを使用せずにGISで簡単にスクリプト化できることです。
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年6月号には、C#を使用したAmoeba Method Optimizationの代替アプローチもあります。


編集

以前に投稿されたコードは、場合によっては対極点に収束しました。@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)
ウーバー

newCenterを中心とした善良な方位角の等距離投影を使用する必要があります。代わりに、N極を中心とした投影を使用し、原点をnewCenterにシフトします。したがって、この場合、適切な解決策を得ることは偶然かもしれません(おそらく、ポイントが互いに近いためでしょうか?)。数千キロ離れた3ポイントで試してみるといいでしょう。正距方位図法の実装は、で与えられるmathworks.com/matlabcentral/fileexchange/...
cffk

@cffk特定の点を中心とする方位角の等距離投影を作成する唯一の他の方法は、同じ方法を使用することですが、esriSRProjCS_WGS1984N_PoleAziEqui(またはesriSRProjCS_WGS1984S_PoleAziEqui)の代わりにesriSRProjCS_World_AzimuthalEquidistantを使用することです。ただし、唯一の違いは、0,90(または0、-90)ではなく0,0を中心としていることです。mathworksでテストを実行して、これが「誠実さ」の予測から異なる結果を生成するかどうかを確認するために私をガイドできますか?
カーククイケンドール

@KirkKuykendall:私の最初の答えの補遺をご覧ください。
cffk

1
@KirkKuykendallでは、ESRIが「誠実さ」の予測を実装したのでしょうか。このアルゴリズムが機能するために必要な重要な特性は、「中心点」から測定された距離が真であることです。そして、このプロパティはチェックするのに十分簡単です。(中心点に対する方位角特性は二次的なものであり、収束率にのみ影響する可能性があります。)
cffk
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.