ArcGIS 10.1을 사용하여 세 점으로 정의 된 측지선 등거리 점을 찾으려면 어떻게해야합니까?


12

예를 들어 해안선에 3 개의 기준점에 대한 좌표가 있고 3 개 지점과 같은 거리에있는 해안에서 떨어진 지점의 좌표를 찾아야합니다. 지오메트리에서는 간단한 연습이지만 모든 측정에는 측지가 고려되어야합니다.

내가 유클리드 방식으로 접근하는 경우 기준점을 연결하는 측지 경로를 측정하고 결과 삼각형의 측면의 중간 점을 찾은 다음 각 경로에 수직 직교를 만들 수 있습니다. 3 개의 엽 소체는 아마도 등거리 지점에서 수렴 될 것이다. 이것이 올바른 방법이라면 Arc에서 더 쉬운 방법이 있어야합니다.

나는 O를 찾아야한다


3 점의 상대 위치에 제약이 있습니까? 그림 동쪽 해안, 중간 지점이 가장 동쪽입니다. 수직선이 해외로 수렴되지 않으므로 솔루션이 작동하지 않습니다. 나는 우리가 다른 나쁜 경우를 생각 해낼 수 있다고 확신합니다!
mkennedy 2016 년

원거리 보존 투영을 사용하고 거기에서 계산을 실행할 수 있는지 궁금합니다. progonos.com/furuti/MapProj/Normal/CartProp/DistPres/... : 확실하지 그것을 할 수있는 알고리즘의, 어쩌면 그것은 중심 (barycentre)입니다 ... 하나가 있어야합니다 en.wikipedia.org/wiki/Barycentric_coordinate_system
알렉스 LEITH

밀접하게 관련된 문제에 대한 솔루션을 찾으 려면 사이트에서 "trilateration"을 검색하십시오 . 또한 gis.stackexchange.com/questions/10332/… 는 복제본이지만 적절한 답변이 없습니다 (대부분 질문이 혼동 된 방식으로 요청 되었기 때문에).
whuber

@mkennedy 원칙적으로 나쁜 경우는 없으며 수치 적으로 불안정한 경우가 있습니다. 이것은 세 개의 기준점이 동일 선상에있을 때 발생합니다. 두 가지 솔루션 (구형 모델)은 공통 측지선의 두 극에서 발생합니다. 타원체 모델에서는 극점이 예상되는 곳 근처에서 발생합니다.
whuber

여기에서 엽소의 사용은 정확하지 않을 것입니다 : 그것들은 수직 이등분자가 아닙니다. 구체에서이 선들은 큰 원 (지오 데식)의 일부이지만 타원체에서는 지오 데 식에서 약간 벗어납니다.
whuber

답변:


10

이 답변은 여러 섹션으로 나뉩니다.

  • "통조 된"루틴으로 원하는 점을 찾는 방법을 보여주는 문제 분석 및 감소 .

  • 그림 : 작업 코드를 제공 하는 작업 프로토 타입

  • 실시 예는 , 용액의 예를 도시.

  • 함정 , 잠재적 문제 및 이에 대처하는 방법을 논의합니다.

  • ArcGIS 구현 , 사용자 정의 ArcGIS 도구 생성 및 필요한 루틴을 얻는 위치에 대한 의견.


문제 분석 및 감소

의는 (완벽 라운드) 구형 모델에 있음을 관찰하여 시작하자 이됩니다 항상 해결책이 될 사실 .. 이대로, 정확히 두 개의 솔루션을 제공합니다. 기준점 A, B 및 C가 주어지면 각 쌍은 "수직 이등분선"을 결정하는데, 이는 주어진 두 점과 등거리에있는 점들의 집합입니다. 이 이등분선은 측지선 (큰 원)입니다. 구형 지오메트리는 타원형입니다 . 두 개의 측지선이 교차합니다 (두 개의 고유 지점에서). 따라서 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)이 공통 거리는 가능한 작습니다.

이 세 거리는 모두 알 수없는 X 에 의존합니다 . 따라서 차이 거리에서 유 (X) = D (X, A) - (D) (X, B) 및 V (X) = D (X, B) - (D) (X, C) X.의 실함수있다 다시, 다소 추상적으로, 우리는 이러한 차이를 순서 쌍으로 모을 수 있습니다. 또한 X의 좌표로 (lat, lon)을 사용하여 X = (phi, lambda)라고도합니다. 이 설정에서 기능

F (phi, 람다) = (u (X), v (X))

2 차원 공간에서 값을 취하는 2 차원 공간의 일부에서 함수이며 우리의 문제는

F (phi, lambda) = (0,0)에 대해 가능한 모든 (phi, lambda)를 찾으십시오.

여기 추상화가 필요한 부분입니다. 이 (순수 수치 다차원 루트 찾기) 문제를 해결하기위한 훌륭한 소프트웨어가 많이 있습니다. 작동 방식은 F 를 계산하는 루틴을 작성한 다음 입력 제한에 대한 정보와 함께 소프트웨어에 전달하는 것입니다 ( phi 는 -90에서 90도 사이에 있어야 하고 람다 는 -180에서 180 사이에 있어야 함) 도). 그것은 1 초 동안 크랭크를 잃어 버리고 (일반적으로) 하나의 값 ( phi , lambda )을 찾을 수 있으면 ( 파이 , 람다 ) 만 반환합니다.

이에 대한 기술이 있기 때문에 다루어야 할 세부 사항 이 있습니다. F "동작 방식 " 에 따라 다양한 솔루션 방법 중에서 선택할 수 있습니다 . 검색을위한 합리적인 시작점을 제공함으로써 소프트웨어를 "조향"하는 데 도움이됩니다 (이것은 다른 솔루션이 아닌 가장 가까운 솔루션을 얻을 수있는 한 가지 방법입니다 ). 일반적으로 솔루션의 정확도를 지정해야합니다 (검색을 중지 할시기를 알 수 있음). (GIS 분석가들이 GIS 문제에서 많이 발생하는 이러한 세부 사항에 대해 알아야 할 사항에 대한 자세한 내용을 보려면 지리 공간 기술 컴퓨터 과학 과정에 포함시킬 권장 주제를 방문 하고 마지막 부분의 "기타"섹션을 참조하십시오. )


일러스트 : 실용 프로토 타입

분석에 따르면 솔루션의 초기 추정치와 F 자체 의 계산이라는 두 가지를 프로그래밍해야 합니다.

초기 추정은 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 좌표이고 두 번째 인수는 y 좌표이며 벡터에 의해 만들어진 각도를 반환합니다 ( x, y).)

두 번째 부분 까지는 ArcGIS 및 거의 모든 다른 GIS와 같은 Mathematica 에는 타원체의 정확한 거리를 계산하는 코드가 포함되어 있기 때문에 작성할 내용이 거의 없습니다. 우리는 단지 루트 찾기 루틴을 호출합니다.

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}

(이것은 미터입니다). 그들은 11 번째 유효 자릿수에 동의합니다 (실제로는 거리가 밀리미터 정도보다 정확하지 않기 때문에 너무 정확합니다). 다음은이 세 가지 점 (검은 색), 세 개의 상호 이등분선 및 솔루션 (빨간색)의 사진입니다.

그림 1


이 구현을 테스트하고 문제의 작동 방식을 더 잘 이해하기 위해 3 개의 넓게 떨어진 기준점에 대한 거리의 제곱 평균 제곱 불일치의 등고선 그림이 있습니다. RMS 불일치는 d (X, A) -d (X, B), d (X, B) -d (X, C) 및 d (X, C) -d (X , A), 제곱의 평균을 구하고 제곱근을 취합니다 .X가 문제를 해결하면 X와 같고 X가 솔루션에서 멀어짐에 따라 증가하므로 어떤 위치에서 솔루션이 "가까이"가되는지를 측정합니다. )

그림 2

기준점 (60, -120), (10, -40) 및 (45,10)은이 Plate Carree 투영에서 빨간색으로 표시됩니다. 계산에 0.03 초가 걸린 솔루션 (49.2644488, -49.9052992)은 노란색입니다. RMS 불일치는 모든 관련 거리가 수천 킬로미터 임에도 불구하고 3 나노 미터 미만 입니다. 어두운 영역은 작은 RMS 값을 나타내고 밝은 영역은 높은 값을 나타냅니다.

이지도는 다른 솔루션이 (-49.2018206, 130.0297177) 근처에 있음을 분명히 보여줍니다 (첫 번째 솔루션의 반대쪽에 초기 검색 값을 설정하여 2 나노 미터의 RMS로 계산 됨).


함정

수치 적 불안정성

기준점이 거의 동일 선상에 있고 서로 가까워지면 모든 솔루션이 거의 반 정도 떨어져 있고 정확하게 고정하기가 매우 어려울 것입니다. 그 이유는 전 세계 어디에서나 기준점을 향하거나 멀어지면서 위치를 조금만 변경해도 거리의 차이가 매우 작게 변화하기 때문입니다. 일반적인 측지 거리 계산에는 결과의 정확도를 높이기위한 정확도와 정밀도가 충분하지 않습니다.

예를 들어 (45.001, 0), (45, 0) 및 (44.999,0)의 기준점부터 시작하여 각 쌍 사이에서 111m 만 Prime Meridian을 따라 분리되어 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에서와 매스 매 티카 코드를. 물론 ArcGIS 자체는 정확한 타원체 거리 계산을 제공합니다. 나머지는 모든 구현 세부 사항입니다. 기준점 좌표를 얻는 방법 (사용자가 입력 한 레이어, 텍스트 파일, 마우스 등) 및 출력을 좌표로 표시하는 방법 결정 화면에 그래픽? 그래픽 레이어? 레이어의 새 점 개체로 표시?)하고 해당 인터페이스를 작성하고 여기에 표시된 Mathematica 코드를 포트 (직선)로 설정하면 모든 설정이 완료됩니다.


3
+1 매우 철저합니다. @whuber에 대해 요금을 청구해야 할 수도 있습니다.
레이더

2
@Radar 감사합니다. 나는 사람들이 내 책이 결국 :-) 나타날 때 내 책을 사기를 바라고있다.
whuber

1
빌을 할 것인가 ... 초안 보내기!

우수한! 여전히 분석 솔루션이 가능한 것 같습니다. 문제는 3 점 A, B, C 및 E를 갖는 3 차원 데카르트 공간으로 복원함으로써 E는 지구의 중심입니다. 다음으로 Plane1과 Plane2 두 평면을 찾으십시오. Plane1은 planeABE에 수직이고 E, midpoint (A, B)를 통과하는 평면입니다. 마찬가지로 Plane2는 planeACE에 수직이고 E, midpoint (C, E)를 통과하는 평면입니다. Plane1과 Plane2의 교점으로 형성된 lineO는 3 점과 등거리에있는 점을 나타냅니다. lineO가 구와 교차하는 A (또는 B 또는 C)에 두 점이 더 가까워지면 pointO가됩니다.
커크 Kuykendall

@Kirk라는 분석 솔루션은 구에만 적용됩니다. (타원체가있는 평면의 교차점은 몇 가지 명백한 예외적 인 경우를 제외하고 는 타원체의 메트릭에서 수직 이등분선이 결코 아닙니다 . 자오선 또는 적도 인 경우)
whuber

3

참고로이 문제는 해상 경계를 결정할 때 발생합니다. 종종 "삼중점"문제라고도하며 Google에서이를 해결할 수있는 몇 가지 논문을 찾을 수 있습니다. 이 논문 중 하나는 저 (!)이며 정확하고 신속하게 수렴하는 솔루션을 제공합니다. http://arxiv.org/abs/1102.1215의 섹션 14 참조

이 방법은 다음 단계로 구성됩니다.

  1. 트라이 포인트 O를 추측
  2. 방위상 등거리 투영의 중심으로 O를 사용
  3. 이 투영에 대한 프로젝트 A, B, C
  4. 이 투영에서 삼점을 찾으십시오
  5. O '를 새로운 투사 중심으로 사용
  6. O '와 O가 일치 할 때까지 반복

투영에서 3 점 솔루션에 필요한 공식은 종이에 나와 있습니다. 정확한 방위각 등거리 투영법을 사용하는 한 정답입니다. 수렴은 2 차적 의미로 몇 번의 반복 만 필요합니다. 이것은 @whuber가 제안한 일반적인 루트 찾기 방법을 능가 할 것입니다.

ArcGIS에서 직접 도와 드릴 수 없습니다. https://pypi.python.org/pypi/geographiclib 에서 측지 계산을 수행하기위한 파이썬 패키지를 가져 와서이를 기반으로 투영을 코딩 할 수 있습니다.


편집하다

@whuber의 퇴보 사례 (45 + eps, 0) (45,0) (45-eps, 0)에서 3 점을 찾는 문제는 Cayley에서 편원 구상 동맥에있는 측지선 인 Phil 의해 고려되었습니다 . 잡지. (1870), http://books.google.com/books?id=4XGIOoCMYYAC&pg=PA15

이 경우 3 점은 방위각 90으로 (45,0)의 측지선을 따르고 측 지척이 사라지는 지점을 찾아서 얻습니다. WGS84 타원체의 경우이 지점은 (-0.10690908732248, 89.89291072793167)입니다. 이 지점에서 (45.001,0), (45,0), (44.999)까지의 거리는 10010287.665788943m (나노 미터 이내)입니다. 이것은 whuber의 추정치보다 약 1882km 더 큽니다 (이 경우가 얼마나 불안정한지를 보여줍니다). 구형 지구의 경우 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

옥타브를 사용하여 이것을 테스트

옥타브 : 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

뉴욕, 도쿄 및 웰링턴의 3 점으로

이 방법은 인접 공 선점에 대해 정확하지 않습니다 (예 : [45.001,0], [45,0], [44.999,0]). 이 경우 방위각 90에서 [45,0]에서 나오는 측지선에서 M 12 = 0을 풀어야합니다 . 다음 함수는 트릭을 수행합니다 (Newton의 방법 사용).

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 차적으로 수렴됩니다. ( 매스 매 티카는 내가 당신의 방법을 참조 진짜 장점은 쉽게 루트 파인더의 사용에 의존하지 않고 GIS에 스크립팅 할 수 있다는 것입니다 각 단계는 코비를 추정하는 네 개의 평가가 필요합니다. 일반적으로 수렴 네 단계에 대해 취하고있다).
whuber

동의한다. 내 방법은 Newton과 동일하므로 Mathematica의 근 발견 방법과 달리 차이를 취하여 기울기를 추정 할 필요가 없습니다.
cffk

맞습니다.하지만 매번 재 투영을해야하는데, 같은 양의 작업 인 것 같습니다. 그것은 것을 즉시 명백하다 : 비록 나는 당신의 접근 방식의 단순함과 우아함을 감사 할 수 있어야 작업을 신속하게 수렴한다.
whuber

내 답변에 동일한 테스트 포인트에 대한 결과를 게시했습니다.
Kirk Kuykendall

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)
whuber

newCenter를 중심으로 정직한 선의 방위각 등거리 투영을 사용해야합니다. 대신 N 극을 중심으로 투영을 사용하고 원점을 newCenter로 이동합니다. 따라서이 경우 적절한 해결책을 얻는 것이 우연 일 수 있습니다 (아마도 점이 서로 가깝기 때문에)? 3km 떨어진 곳에서 3 포인트 떨어져서 사용해 보는 것이 좋습니다. 방위각 등거리 투사의 구현에 제시되어있다 mathworks.com/matlabcentral/fileexchange/...
cffk

@cffk 특정 지점을 중심으로 방위각 등거리 투영을 생성하는 유일한 방법은 동일한 방법을 사용하지만 esriSRProjCS_World_AzimuthalEquidistant 대신 esriSRProjCS_WGS1984N_PoleAziEqui (또는 esriSRProjCS_WGS1984S_PoleAziEqui)를 사용하는 것입니다. 그러나 유일한 차이점은 0,90 (또는 0, -90) 대신 0,0을 중심으로한다는 것입니다. 이것이 "정직한 선에서 좋은"영사와 다른 결과를 낳는 지 확인하기 위해 mathworks로 테스트를 실행하도록 안내해 주실 수 있습니까?
Kirk Kuykendall

@ KirkKuykendall : 첫 번째 답변에 대한 부록을 참조하십시오.
cffk

1
@KirkKuykendall 아마도 ESRI가 "정직한 선 (Hoest-to-Goodness)"프로젝션을 구현했을까요? 이 알고리즘이 작동하는 데 필요한 주요 특성은 "중심점"에서 측정 된 거리가 참이라는 것입니다. 그리고이 속성은 확인하기 쉽습니다. (중심점에 대한 방위 특성은 2 차이며 수렴 속도에만 영향을 줄 수 있습니다.)
cffk
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.