如何使用ArcGIS 10.1查找由三个点定义的测地等距点?


12

例如,我有一条海岸线上三个基点的坐标,我需要找到离这三个点等距的海岸外点的坐标。这是一个简单的几何练习,但是所有测量都必须考虑大地测量。

如果我以欧几里得方式接近,则可以测量连接基点的测地线路径,找到所得三角形侧面的中点,并为每个路径创建正交正交体。这三个竞技场大概会在等距点收敛。如果这是正确的方法,那么必须在Arc中找到一种更简单的方法。

我需要找到O


3点的相对位置是否有限制?图片东海岸,中点最东。您的解决方案将无法正常工作,因为垂直方向不会在海上收敛。我确定我们可以提出其他不良案例!
mkennedy

我想知道您是否可以使用距离保持投影并从那里运行计算?progonos.com/furuti/MapProj/Normal/CartProp/DistPres/… 不确定执行此算法的方法,肯定有一个...也许是重心:en.wikipedia.org/wiki/Barycentric_coordinate_system
Alex Leith

对于与一个紧密相关的问题的解决方案,请在我们的网站上搜索“三边测量”。另外,gis.stackexchange.com/questions/10332/…是重复的,但是没有足够的答案(很可能是因为问题以混乱的方式提出)。
ub

@mkennedy原则上,没有不好的情况,只有数字上不稳定的情况。当三个基点共线时,就会发生这种情况。这两个解(在球形模型上)出现在共同测地线的两个极点处;在椭球模型中,它们发生在预期极点附近。
ub

在这里使用后院是不正确的:它们不是垂直平分线。在球体上,这些线将成为大圆的一部分(大地测量学),但是在椭圆体上,它们将略微偏离大地测量学。
ub

Answers:


10

该答案分为多个部分:

  • 问题的分析和归纳,展示了如何使用“固定”例程找到所需的点。

  • 插图:工作原型,给出工作代码。

  • Example,显示解决方案的示例。

  • 陷阱,讨论潜在的问题以及如何解决它们。

  • ArcGIS实施,有关创建自定义ArcGIS工具以及在何处获取所需例程的注释。


问题分析与归约

让我们开始观察,在(完全圆形)球形模型中总会有一个解 -实际上,只有两个解。给定基点A,B和C,每对确定其“垂直二等分线”,这是与两个给定点等距的点集。该等分线是一个测地线(大圆)。球形几何形状是椭圆形的:任意两个测地线相交(在两个唯一的点)。因此,根据定义,AB的平分线与BC的平分线的交点与A,B和C等距,从而解决了这个问题。(请参见下面的第一个图。)

椭球上的事物看起来更复杂,但是由于它是球体的微小扰动,因此我们可以期待类似的行为。(对此进行分析会使我们走得太远了。)(在GIS内部)用于计算椭球上精确距离的复杂公式并不是概念上的复杂问题:问题基本上是相同的。 要了解问题的实质有多简单,让我们对其进行抽象描述。在此陈述中,“ 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(phi,lambda)=(u(X),v(X))

是来自二维空间的一部分的函数,在二维空间中取值,我们的问题简化为

找出所有可能的(phi,lambda),其中F(phi,lambda)=(0,0)。

这是抽象的回报: 存在许多出色的软件来解决此(纯数字多维根查找)问题。 它的工作方式是编写一个例程来计算F,然后将其连同有关其输入限制的任何信息一起传递给软件(phi必须在-90到90度之间,lambda必须在-180到180之间度)。如果它能找到一个值,它就会急转一秒,然后(通常)仅返回(philambda)的一个值。

有很多细节要处理,因为这是一门艺术:根据F的 “行为”,有多种解决方法可供选择。通过提供合理的搜索起点来帮助“引导”软件(这是我们可以获得最接近的解决方案的一种方法,而不是其他任何一种解决方案);并且您通常需要指定解决方案的精确度(以便知道何时停止搜索)。(有关GIS分析人员需要了解的哪些细节的更多信息,这些细节在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坐标,并且它返回由向量形成的角度( 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
   ];

此实现最值得注意的方面是如何通过始终分别以180度和360度为模数来计算来限制纬度(f)和经度(q)的需求。这避免了必须约束问题(通常会带来麻烦)。对控制参数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


为了测试此实现并更好地理解问题的表现,这是三个距离较远的基点的距离均方根差异的等高线图。(RMS差异通过计算所有三个差异d(X,A)-d(X,B),d(X,B)-d(X,C)和d(X,C)-d(X ,A),取其平方的平均值,然后取平方根。当X解决问题时,它等于零;否则,当X离开解决方案而增加时,该值等于0,从而衡量我们在任何位置成为解决方案的“接近度”。 )

图2

基数(60,-120),(10,-40)和(45,10)在此Plate Carree投影中以红色显示;解决方案(49.2644488,-49.9052992)为黄色,需要0.03秒才能计算出来。尽管所有相关距离均为数千公里,但其RMS差异小于3 纳米。深色区域显示的是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在做数学代码。当然,ArcGIS本身可以提供准确的椭圆距离计算。剩下的就是所有实现细节:决定如何获取基点坐标(从图层中?由用户键入?从文本文件中?从鼠标中获取?)以及如何显示输出(作为坐标)显示在屏幕上吗(作为图形点?作为图层中的新点对象?),编写该界面,移植此处显示的Mathematica代码(简单明了),您便一切就绪。


3
+1非常彻底。我认为您可能必须为此付费,@ whuber。
雷达

2
@Radar谢谢。我希望人们能在书本最终出现时购买它:-)。
ub

1
将做比尔...发送草稿!!!

优秀的!不过,似乎有一种分析解决方案是可行的。通过将问题重述为具有3个点A,B,C和E的3d笛卡尔空间,其中E是地球的中心。接下来找到两个平面Plane1和Plane2。Plane1是垂直于planeABE并经过E,midpoint(A,B)的平面。同样,Plane2将是垂直于planeACE并经过E,midpoint(C,E)的平面。由平面1和平面2的交点形成的直线O表示与3个点等距的点。这两个点中最接近的点是A(或B或C),其中lineO与球面相交的点是pointO。
Kirk Kuykendall 2013年

该分析解决方案@Kirk仅适用于球体。(除了一些明显的特殊情况:当它们是子午线或赤道时,带有椭球面的平面的交点在椭球面上的度量永远不会是垂直平分线。)
胡伯

3

如您所注意到的,这个问题是在确定海域边界时出现的。它通常被称为“三点”问题,您可以在Google上查找并找到解决该问题的几篇论文。这些论文之一是由我(!)撰写的,我提供了一种准确且迅速收敛的解决方案。参见http://arxiv.org/abs/1102.1215的第14节

该方法包括以下步骤:

  1. 猜一个三点O
  2. 使用O作为方位角等距投影的中心
  3. 项目A,B,C,为此投影
  4. 在这个投影中找到三点,O'
  5. 用O'作为新的投影中心
  6. 重复直到O'和O重合

给出了投影中三点解的必要公式。只要您使用的是精确的方位角等距投影,答案都是准确的。收敛是二次的,意味着只需要进行几次迭代即可。这几乎肯定会胜过@whuber建议的一般根查找方法。

我无法直接帮助您使用ArcGIS。您可以从https://pypi.python.org/pypi/geographiclib中获取我的python软件包来进行测地线计算, 并基于此对投影进行编码很简单。


编辑

Cayley在《关于扁球体上的测地线上,Phil。》中考虑了在@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 m(在一个纳米左右)。这比whuber的估计大约要多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

我用八度测试

八度: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],此方法不准确。在这种情况下,应该在方位角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。但是,目前尚不清楚一般的根查找器的性能是否会较差:该函数在最佳状态下表现良好,例如,牛顿法也将二次收敛。(Mathematica通常会收敛大约四个步骤;每个步骤需要进行四个评估才能估计Jacobian值。)我对您的方法所看到的真正好处是,它可以很容易地在GIS中编写脚本,而无需求助于寻根器。
ub

我同意。我的方法等效于牛顿,因此,与Mathematica的根查找方法相比,无需通过求差来估计梯度。
cffk

是的-但是您每次都必须进行重新投影,看起来工作量差不多。不过,我确实欣赏您的方法的简单性和优雅性:很明显,它应该有效并且会很快收敛。
ub

我已经在答案中发布了相同测试点的结果。
Kirk Kuykendall 2013年

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年6月发行的MSDN杂志中还有另一种方法,即使用C#的Amoeba方法优化


编辑

在某些情况下,先前发布的代码会收敛到对映体。我更改了代码,以便为@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 2013年

@cffk我看到的另一种创建以特定点为中心的方位角等距投影的方法是使用相同的方法,但是使用esriSRProjCS_World_AzimuthalEquidistant而不是esriSRProjCS_WGS1984N_PoleAziEqui(或esriSRProjCS_WGS1984S_PoleAziEqui)。但是,唯一的区别是它以0,0而不是0,90(或0,-90)为中心。您能否指导我使用mathworks进行测试,以查看这是否与“诚实至善”预测产生不同的结果?
Kirk Kuykendall,2013年

@KirkKuykendall:参见我的第一个答案的附录。
cffk 2013年

1
@KirkKuykendall那么,也许ESRI确实实现了“诚实至善”的预测?该算法工作所需的关键属性是,距“中心点”的距离为真。而且此属性很容易检查。(相对于中心点的方位角属性是次要的,可能仅影响收敛速度。)
cffk 2013年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.