Üç nokta ile tanımlanan jeodezik eşit uzaklıkta bir nokta bulmak için ArcGIS 10.1'i nasıl kullanabilirim?


12

Örneğin, bir sahil şeridindeki üç temel nokta için koordinatım var ve bu üç kıyıdan eşit uzaklıkta olan sahilin dışındaki noktanın koordinatlarını bulmam gerekiyor. Geometride basit bir alıştırmadır, ancak tüm ölçümler jeodeziyi dikkate almalıdır.

Buna Öklid tarzında yaklaşıyor olsaydım, temel noktaları birleştiren jeodezik yolları ölçebilir, elde edilen üçgenin kenarlarının orta noktalarını bulabilir ve bu yolların her birine dikey ortodromlar yaratabilirdim. Üç loxodrom muhtemelen eşit mesafede birleşir. Bu doğru yöntemse, Arc'da yapmanın daha kolay bir yolu olmalı.

O bulmam gerek


3 noktanın göreceli pozisyonlarında kısıtlamalar var mı? Resim doğu sahili, orta nokta en doğu doğusudur. Dikeyler denizde birleşmediği için çözümünüz işe yaramayacaktı. Eminim başka kötü durumlar da bulabiliriz!
mkennedy

Acaba bir mesafeyi koruyan projeksiyon kullanıp hesaplamayı oradan yapabilir misiniz? progonos.com/furuti/MapProj/Normal/CartProp/DistPres/… Bunu yapmak için algoritmadan emin değilim, bir tane olmalı ... belki de bariyer: en.wikipedia.org/wiki/Barycentric_coordinate_system
Alex Leith

Yakından ilgili bir soruna çözüm bulmak için sitemizde "üçlü" arama yapın . Ayrıca, gis.stackexchange.com/questions/10332/… bir kopyadır , ancak yeterli cevabı yoktur (büyük olasılıkla soru karışık bir şekilde sorulduğu için).
whuber

Prensip olarak, kötü durumlar yoktur, sadece sayısal olarak dengesiz olanlar vardır. Bunlar, üç temel nokta eşdoğrusal olduğunda meydana gelir; iki çözüm (küresel bir modelde) ortak jeodeziklerin iki kutbunda meydana gelir; elipsoidal bir modelde, bu kutupların bekleneceği yerde meydana gelirler.
whuber

Burada loxodromların kullanımı doğru olmaz: bunlar dik dikiciler değildir. Kürede, bu çizgiler büyük dairelerin (jeodezikler) parçaları olacak, ancak elipsoid üzerinde, jeodezik olmaktan biraz ayrılacaklar.
whuber

Yanıtlar:


10

Bu cevap birden fazla bölüme ayrılmıştır:

  • "Hazır" rutinler ile istenen noktanın nasıl bulunacağını gösteren Problemin Analizi ve Azaltılması .

  • İllüstrasyon: Çalışma kodu veren bir Çalışma Prototipi .

  • Örnek , çözeltiler örneklerini gösteren.

  • Tuzaklar , potansiyel sorunları ve onlarla nasıl başa çıkılacağını tartışıyor.

  • ArcGIS uygulaması , özel bir ArcGIS aracı oluşturma ve gerekli rutinlerin nereden alınacağı hakkında yorumlar.


Sorunun Analizi ve Azaltılması

(Mükemmel yuvarlak) küresel modelde her zaman bir çözüm olacağını gözlemleyerek başlayalım - aslında, tam olarak iki çözüm. A, B ve C taban noktaları verildiğinde, her bir çift, verilen iki noktadan eşit uzaklıkta olan noktalar kümesi olan "dikey açıortayını" belirler. Bu açıortay jeodeziktir (büyük daire). Küresel geometri eliptiktir : herhangi iki jeodezik kesişir (iki benzersiz noktada). Böylece, AB'nin ve BC'nin açıcının kesişme noktaları - tanım gereği - A, B ve C'den eşit uzaklıkta olup, sorunu çözmektedir. (Aşağıdaki ilk şekle bakın.)

Elipsoidde işler daha karmaşık görünüyor, ancak kürenin küçük bir pertürbasyonu olduğu için benzer davranışlar bekleyebiliriz. (Bunun analizi bizi çok uzağa götürür.) Bir elipsoid üzerinde doğru mesafeleri hesaplamak için kullanılan karmaşık formüller (dahili olarak bir CBS içinde) kavramsal bir komplikasyon değildir, sorun temelde aynıdır. Sorunun ne kadar basit olduğunu görmek için, biraz soyut olarak belirtelim. Bu ifadede, "d (U, V)" U ve V noktaları arasındaki gerçek, tamamen doğru mesafeyi ifade eder.

Elipsoid üzerinde üç nokta A, B, C (lat-lon çiftleri olarak) verildiğinde, (1) d (X, A) = d (X, B) = d (X, C) ve ( 2) bu ortak mesafe mümkün olduğunca küçüktür.

Bu üç mesafenin tümü bilinmeyen X'e bağlıdır . Böylece u (X) = d (X, A) - d (X, B) ve v (X) = d (X, B) - d (X, C) mesafelerindeki farklılıklar X'in gerçek değerli işlevleridir. Yine, soyut bir şekilde, bu farklılıkları düzenli bir çift olarak bir araya getirebiliriz. Ayrıca X için koordinatlar olarak (lat, lon) kullanacağız, bunu da düzenli bir çift olarak düşünmemize izin verin, örneğin X = (phi, lambda). Bu kurulumda, işlev

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

iki boyutlu bir uzayın bir kısmından iki boyutlu uzayda değerler alan bir fonksiyon ve sorunumuz

F (phi, lambda) = (0,0) olan tüm olasıları (phi, lambda) bulun.

İşte bu noktada soyutlama karşılığını verir: Bu (tamamen sayısal çok boyutlu kök bulma) problemini çözmek için birçok harika yazılım mevcuttur. Çalışma şekli, F'yi hesaplamak için bir rutin yazmanız , daha sonra girişindeki kısıtlamalar hakkında herhangi bir bilgi ile birlikte yazılıma aktarmanızdır ( phi -90 ile 90 derece arasında olmalı ve lambda -180 ile 180 arasında olmalıdır) derece). Saniyenin bir kısmı için krank eder ve bulabilirse (tipik olarak) yalnızca bir değer ( phi , lambda ) döndürür .

Ele alınacak ayrıntılar var , çünkü bunun bir sanatı var: F'nin nasıl davrandığına bağlı olarak seçim yapabileceğiniz çeşitli çözüm yöntemleri var ; arama için makul bir başlangıç ​​noktası vererek yazılımı "yönlendirmeye" yardımcı olur (bu, başka herhangi bir çözüm yerine en yakın çözümü elde etmenin bir yoludur ); ve genellikle çözümün ne kadar doğru olmasını istediğinizi belirtmeniz gerekir (böylece aramayı ne zaman durduracağını bilir). (CBS analistlerinin CBS problemlerinde çok fazla ortaya çıkan bu tür ayrıntılar hakkında bilmesi gerekenler hakkında daha fazla bilgi için, lütfen Jeo-Uzamsal Teknolojiler için Bilgisayar Bilimi dersine dahil edilecek konuları önerin bölümünü ziyaret edin ve sondaki "Çeşitli" bölümüne bakın. )


Örnek: Çalışan bir Prototip

Analiz, iki şeyi programlamamız gerektiğini gösteriyor: çözümün kaba bir başlangıç ​​tahmini ve F'nin hesaplanması .

İlk tahmin, üç temel noktanın "küresel ortalaması" ile yapılabilir. Bu, onları jeosantrik Kartezyen (x, y, z) koordinatlarında temsil ederek, bu koordinatların ortalamasını alarak ve bu ortalamayı küreye geri yansıtarak ve enlem ve boylamda yeniden ifade ederek elde edilir. Kürenin büyüklüğü önemsizdir ve hesaplamalar böylece basit bir şekilde yapılır: çünkü bu sadece bir başlangıç ​​noktası olduğundan, elipsoidal hesaplamalara ihtiyacımız yok.

Bu çalışma prototipi için Mathematica 8 kullandım .

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
  ]

(Son Ifkoşul, ortalamanın bir boylamı belirtmek için açıkça başarısız olup olamayacağını test eder; öyleyse, girdisinin enlem ve boylamlarının düz bir aritmetik ortalamasına geri döner - belki de mükemmel bir seçim değil, en azından geçerli bir seçimdir. Bu kodu uygulama rehberliği için kullananlar için, Mathematica'nın argümanlarının ArcTandiğer birçok uygulamaya kıyasla tersine çevrildiğine dikkat edin : ilk argümanı x koordinatı, ikincisi y koordinatıdır ve vektörün yaptığı açıyı döndürür ( x, y)).

İkinci bölüme gelince, Mathematica - ArcGIS ve neredeyse tüm diğer GIS'ler gibi - elipsoid üzerinde doğru mesafeleri hesaplamak için kod içerdiğinden, yazacak neredeyse hiçbir şey yok. Sadece kök bulma rutini diyoruz:

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
   ];

Bu uygulamanın en dikkat çekici yanı , sırasıyla enlem ( f) ve boylam ( q) değerlerini her zaman sırasıyla 180 ve 360 ​​derece modulo hesaplayarak nasıl sınırlandırması gerektiğidir . Bu, sorunu sınırlamaktan kaçınır (bu genellikle komplikasyonlar yaratır). Kontrol parametreleri MaxIterationsvb., Bu kodun mümkün olan en yüksek doğruluğu sağlaması için ayarlanmıştır.

Eylem halinde görmek için, ilgili bir soruda verilen üç temel noktaya uygulayalım :

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

{-6.29692, 106.907}

Bu çözüm ile üç nokta arasındaki hesaplanan mesafeler

{1450.23206979, 1450.23206979, 1450.23206978}

(bunlar metredir). Onbirinci önemli basamaktan anlaşıyorlar (mesafeler nadiren bir milimetreden daha iyiye doğru olduğu için çok kesin). İşte bu üç noktanın (siyah), üç karşılıklı açılayıcılarının ve çözümünün (kırmızı) bir resmi:

Şekil 1


Misal

Bu uygulamayı test etmek ve sorunun nasıl davrandığını daha iyi anlamak için, burada üç geniş aralıklı taban noktası için mesafelerde kök ortalama kare tutarsızlığının bir kontur grafiği vardır. (RMS tutarsızlığı, üç (d, X, A) -d (X, B), d (X, B) -d (X, C) ve d (X, C) -d (X) farklarının hesaplanmasıyla elde edilir. , A), karelerinin ortalamasını almak ve karekök almak X'in problemi çözdüğü zaman sıfıra eşittir ve aksi takdirde X bir çözeltiden uzaklaştıkça artar ve böylece herhangi bir yerde ne kadar "yakın" olduğumuzu ölçer. )

şekil 2

Temel noktalar (60, -120), (10, -40) ve (45,10) bu Plaka Carree projeksiyonunda kırmızı renkte gösterilmiştir; hesaplamak için 0.03 saniye gerektiren çözelti (49.2644488, -49.9052992) sarı renktedir. RMS tutarsızlığı, ilgili tüm mesafeler binlerce kilometre olmasına rağmen, üç nanometreden daha azdır . Karanlık alanlar RMS'nin küçük değerlerini ve açık alanlar yüksek değerler gösterir.

Bu harita açıkça (-49.2018206, 130.0297177) yakınında bulunan başka bir çözüm olduğunu göstermektedir (ilk arama değerini ilk çözümün çapına zıt olarak ayarlayarak iki nanometrelik bir RMS ile hesaplanmıştır.)


tuzaklar

Sayısal dengesizlik

Temel noktalar neredeyse birbirine yakın ve birbirine yakın olduğunda, tüm çözümler neredeyse yarım dünya uzakta olacak ve doğru bir şekilde tespit edilmesi son derece zor olacaktır. Bunun nedeni, dünyadaki bir konumdaki küçük değişikliklerin - onu taban noktalarına doğru veya uzağa doğru hareket ettirmenin - mesafeler arasındaki farklılıklarda inanılmaz derecede küçük değişikliklere yol açmasıdır. Sonucu belirlemek için jeodezik mesafelerin olağan hesaplamasında yeterli doğruluk ve kesinlik yoktur.

Örneğin, Prime Meridian boyunca her bir çift arasında sadece 111 metre ayrılan (45.001, 0), (45, 0) ve (44.999,0) temel noktalardan başlayarak triçözümü alır (11.8213, 77.745) ). Ondan taban noktalara olan uzaklık 8.127.964.998 77; 8.127.964.998 41; ve sırasıyla 8.127.964.998 65 metre. En yakın milimetreyi kabul ediyorlar! Bu sonucun ne kadar doğru olabileceğinden emin değilim, ancak diğer uygulamalar bu konumdan çok uzak konumlar döndürdüğünde ve üç mesafenin neredeyse eşitliğini gösterdiyse en azından şaşırmazdı.

Hesaplama süresi

Bu hesaplamalar, karmaşık mesafe hesaplamaları kullanarak hatırı sayılır araştırmalar içerdiğinden, hızlı değildir ve genellikle bir saniyenin gözle görülür bir kısmını gerektirir. Gerçek zamanlı uygulamaların bunun farkında olması gerekir.


ArcGIS uygulaması

Python, ArcGIS için tercih edilen kodlama ortamıdır (sürüm 9'dan başlayarak). Scipy.optimize paketi çok değişkenli rootfinder sahiptir rootyapmalı FindRootyapar Mathematica kodu. Elbette ArcGIS'in kendisi doğru elipsoidal mesafe hesaplamaları sunar. Geri kalan, tüm uygulama detaylarıdır: taban noktası koordinatlarının nasıl elde edileceğine (kullanıcı tarafından yazılan bir katmandan? Bir metin dosyasından? Fareden?) Ve çıktının nasıl sunulacağına (koordinatlar olarak) karar verin ekranda bir grafik noktası olarak? bir katmandaki yeni bir nokta nesnesi olarak?) görüntüleniyorsa, o arabirimi yazın, burada gösterilen Mathematica kodunu (basit) bağlayın ve hepiniz ayarlanacaktır.


3
+1 Çok ayrıntılı. Sanırım bunun için şarj etmeye başlayabilirsin, @whuber.
Radar

2
@Radar Teşekkürler. İnsanların kitabımı sonunda (hiç) göründüğünde satın almasını umuyorum :-).
whuber

1
Bill yapacak ... Taslak gönder !!!

Mükemmel! Yine de analitik bir çözümün mümkün olduğu anlaşılıyor. Sorunu 3 nokta A, B, C ve E ile 3 boyutlu kartezyen alana yeniden yerleştirerek E'nin dünyanın merkezi olduğu yer. Sonra iki uçak Plane1 ve Plane2 bulun. Düzlem1, ABE düzlemine normal olan ve E, orta noktasından (A, B) geçen düzlemdir. Benzer şekilde, Düzlem2, düz-düzleme normal olan ve E, orta noktasından (C, E) geçen düzlem olacaktır. Düzlem1 ve Düzlem2'nin kesişmesiyle oluşan çizgiO, 3 noktaya eşit noktaları temsil eder. İki noktanın lineO'nun küreyle kesiştiği A (veya B veya C) 'ye daha yakın olması noktaO'dur.
Kirk Kuykendall

Bu analitik çözüm, @Kirk, sadece küre için geçerlidir. (Elipsoid ile düzlemlerin
kesişimleri

3

Belirttiğiniz gibi, bu sorun deniz sınırlarının belirlenmesinde ortaya çıkar; genellikle "üç nokta" sorunu olarak adlandırılır ve bunu Google'a gönderebilir ve bunu ele alan birkaç makale bulabilirsiniz. Bu belgelerden biri benim (!) Ve doğru ve hızla yakınsak bir çözüm sunuyorum. Bkz. Bölüm 14, http://arxiv.org/abs/1102.1215

Yöntem aşağıdaki adımlardan oluşur:

  1. bir üç nokta O tahmin et
  2. O'yu azimuthal eşitlikli projeksiyonun merkezi olarak kullanın
  3. A, B, C projesi bu projeksiyona
  4. bu izdüşümdeki üç noktayı bul, O '
  5. O 'yu yeni projeksiyon merkezi olarak kullanın
  6. O 've O çakışana kadar tekrarlayın

Projeksiyondaki üç noktalı çözelti için gerekli formül makalede verilmiştir. Doğru bir azimuthal eşitlikli projeksiyon kullandığınız sürece, cevap tam olacaktır. Yakınsama kuadratiktir, sadece birkaç tekrarlamaya ihtiyaç vardır. Bu, @whuber tarafından önerilen genel kök bulma yöntemlerinden neredeyse kesinlikle daha iyi performans gösterecektir.

ArcGIS ile size doğrudan yardım edemem. Python paketimi https://pypi.python.org/pypi/geographiclib adresinden jeodezik hesaplamalar yapmak için alabilir ve projeksiyonu buna göre kodlamak basittir.


Düzenle

@ Whuber'ın dejenere olgusunda (45 + eps, 0) (45,0) (45-eps, 0) üç noktayı bulma problemi Cayley tarafından oblate bir küredeki jeodezik hatlarda düşünülmüştür Phil. Mag. (1870), http://books.google.com/books?id=4XGIOoCMYYAC&pg=PA15

Bu durumda, üç nokta azimut 90 ile (45,0) bir jeodezik takip edilerek ve jeodezik ölçeğin yok olduğu noktayı bularak elde edilir. WGS84 elipsoid için bu nokta (-0.10690908732248, 89.89291072793167). Bu noktadan (45.001,0), (45,0), (44.999) her birine olan mesafe 10010287.665788943 m'dir (bir nanometre içinde). Bu, whuber'ın tahmininden yaklaşık 1882 km daha fazladır (bu, sadece bu davanın ne kadar kararsız olduğunu gösterir). Küresel bir dünya için üç nokta elbette (0,90) veya (0, -90) olacaktır.

ADDENDUM: İşte Matlab kullanarak azimuthal eşitlik yönteminin bir uygulaması

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

Bunu Octave kullanarak test ediyorum

oktav: 1> format uzun
oktav: 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

New York, Tokyo ve Wellington için üç nokta.

Bu yöntem, komşu lineer noktalar için yanlıştır, örneğin [45.001,0], [45,0], [44.999,0]. Bu durumda, azimut 90'da [45,0] 'dan çıkan bir jeodezikte M 12 = 0 için çözmelisiniz . Aşağıdaki işlev hile yapar (Newton'un yöntemini kullanarak):

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

Örnek olarak, bu:

[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. Bununla birlikte, genel bir kök bulucunun daha az iyi bir performans göstereceği belirsizdir: fonksiyon optimum ve Newton'un yöntemine yakın bir şekilde güzel davranır, ayrıca kuadratik olarak birleşir. ( Mathematica genellikle yakınsama için yaklaşık dört adım almaktadır; her adımda Jacobian'ı tahmin etmek için dört değerlendirme gerekir.) Metodunuza gördüğüm gerçek avantaj, bir kök bulucu kullanmaya başvurmadan kolayca bir CBS'de yazılabilmesidir.
whuber

Katılıyorum. Benim yöntemim Newton ile eşdeğerdir ve Mathematica'nın kök bulma yönteminin aksine, degradeyi farklılıklar alarak tahmin etmeye gerek yoktur.
cffk

Doğru - ama her seferinde yeniden projeksiyon yapmalısınız, bu da aynı miktarda iş gibi görünüyor. O olduğunu hemen belli: gerçi ben senin yaklaşımın sadeliği ve şıklığı takdir ediyoruz gerektiğini çalışmak ve hızla araya gelecek.
whuber

Cevabımda aynı test puanları için sonuçlar yayınladım.
Kirk Kuykendall

3

@ Cffk yaklaşımının bir çözüm üzerinde ne kadar çabuk birleştiğini merak ettim, bu yüzden bu çıktıyı üreten arcobjects kullanarak bir test yazdım. Mesafeler metre cinsindendir:

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

İşte kaynak kodu. (Düzenle) FindCircleCenter'ı, azimuttal projeksiyonun kenarından düşen kavşakları (merkez noktaları) işleyecek şekilde değiştirdi:

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, C # kullanarak Amoeba Method Optimization'ın Haziran 2013 sayısında da alternatif bir yaklaşım var .


Düzenle

Daha önce yayınlanan kod bazı durumlarda antipoda yakınlaştı. @ Cffk test noktaları için bu çıktıyı üretecek şekilde kodu değiştirdim.

Şimdi ürettiği çıktı:

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

Revize edilmiş kod:

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;
    }
}

Düzenle

İşte esriSRProjCS_WGS1984N_PoleAziEqui ile elde ettiğim sonuçlar

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

Bu etkileyici bir şekilde yakınsama! (+1)
whuber

NewCenter merkezli dürüst-iyilik azimuthal eşitlikli bir projeksiyon kullanmalısınız. Bunun yerine, N kutbu üzerinde bulunan projeksiyonu kullanıyorsunuz ve başlangıç ​​noktasını newCenter'a kaydırıyorsunuz. Bu nedenle, bu durumda iyi bir çözüm bulmanız kazara olabilir (belki de noktalar birbirine yakın olduğu için?). Binlerce kilometre uzaklıktaki 3 puanla denemek iyi olur. Azimuthal eşitlikli
cffk

@cffk Belirli bir nokta üzerinde ortalanmış azimuthal eşitlikli bir projeksiyon oluşturmanın diğer tek yolu aynı yöntemi kullanmaktır ancak esriSRProjCS_World_AzimuthalEquidistant yerine esriSRProjCS_WGS1984N_PoleAziEqui (veya esriSRProjCS_WGS19E19A). Ancak tek fark, 0,90 (veya 0, -90) yerine 0,0 üzerinde odaklanmasıdır. Bunun "dürüst-iyilik" projeksiyonundan farklı sonuçlar üretip getirmediğini görmek için matematik çalışmaları ile bir test yaparken bana rehberlik edebilir misiniz?
Kirk Kuykendall

@KirkKuykendall: ilk cevabımın ekine bakınız.
cffk

1
@KirkKuykendall Belki ESRI bir "dürüst-iyilik" projeksiyonu uyguladı? Bu algoritmanın çalışması için gereken anahtar özellik, "merkez noktadan" ölçülen mesafelerin doğru olmasıdır. Ve bu özellik kontrol etmek için yeterince kolaydır. (Merkez noktaya göre azimuthal özelliği ikincildir ve sadece yakınsama oranını etkileyebilir.)
cffk
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.