例如,我有一条海岸线上三个基点的坐标,我需要找到离这三个点等距的海岸外点的坐标。这是一个简单的几何练习,但是所有测量都必须考虑大地测量。
如果我以欧几里得方式接近,则可以测量连接基点的测地线路径,找到所得三角形侧面的中点,并为每个路径创建正交正交体。这三个竞技场大概会在等距点收敛。如果这是正确的方法,那么必须在Arc中找到一种更简单的方法。
例如,我有一条海岸线上三个基点的坐标,我需要找到离这三个点等距的海岸外点的坐标。这是一个简单的几何练习,但是所有测量都必须考虑大地测量。
如果我以欧几里得方式接近,则可以测量连接基点的测地线路径,找到所得三角形侧面的中点,并为每个路径创建正交正交体。这三个竞技场大概会在等距点收敛。如果这是正确的方法,那么必须在Arc中找到一种更简单的方法。
Answers:
该答案分为多个部分:
问题的分析和归纳,展示了如何使用“固定”例程找到所需的点。
插图:工作原型,给出工作代码。
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之间度)。如果它能找到一个值,它就会急转一秒,然后(通常)仅返回(phi,lambda)的一个值。
有很多细节要处理,因为这是一门艺术:根据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个有效数字(实际上太精确了,因为距离很少精确到优于毫米左右)。这是这三个点(黑色),它们的三个相互平分线和解(红色)的图片:
为了测试此实现并更好地理解问题的表现,这是三个距离较远的基点的距离均方根差异的等高线图。(RMS差异通过计算所有三个差异d(X,A)-d(X,B),d(X,B)-d(X,C)和d(X,C)-d(X ,A),取其平方的平均值,然后取平方根。当X解决问题时,它等于零;否则,当X离开解决方案而增加时,该值等于0,从而衡量我们在任何位置成为解决方案的“接近度”。 )
基数(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米。他们同意最接近的毫米!我不确定这个结果可能有多精确,但是如果其他实现返回的位置离此距离很远,则显示出三个距离几乎相等,就不会感到惊讶。
计算时间
这些计算由于涉及使用复杂距离计算的大量搜索,因此计算速度并不快,通常需要几分之一秒的时间。实时应用程序需要意识到这一点。
Python是ArcGIS(从版本9开始)的首选脚本环境。该scipy.optimize包有一个多元rootfinder root
它应该做的事FindRoot
在做数学代码。当然,ArcGIS本身可以提供准确的椭圆距离计算。剩下的就是所有实现细节:决定如何获取基点坐标(从图层中?由用户键入?从文本文件中?从鼠标中获取?)以及如何显示输出(作为坐标)显示在屏幕上吗(作为图形点?作为图层中的新点对象?),编写该界面,移植此处显示的Mathematica代码(简单明了),您便一切就绪。
如您所注意到的,这个问题是在确定海域边界时出现的。它通常被称为“三点”问题,您可以在Google上查找并找到解决该问题的几篇论文。这些论文之一是由我(!)撰写的,我提供了一种准确且迅速收敛的解决方案。参见http://arxiv.org/abs/1102.1215的第14节
该方法包括以下步骤:
给出了投影中三点解的必要公式。只要您使用的是精确的方位角等距投影,答案都是准确的。收敛是二次的,意味着只需要进行几次迭代即可。这几乎肯定会胜过@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
我很想知道@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