Come posso usare ArcGIS 10.1 per trovare un punto equidistante geodetico definito da tre punti?


12

Ad esempio, ho coordinate per tre punti base su una costa e ho bisogno di trovare le coordinate del punto al largo della costa che è equidistante da tutti e tre. È un semplice esercizio di geometria, ma tutte le misurazioni devono tenere conto della geodesia.

Se mi stavo avvicinando a questo in modo euclideo, potrei misurare i percorsi geodetici che collegano i punti di base, trovare i punti medi dei lati del triangolo risultante e creare ortogonali perpendicolari a ciascuno di quei percorsi. I tre loxodromi dovrebbero presumibilmente convergere nel punto equidistante. Se questo è il metodo corretto, ci deve essere un modo più semplice per farlo in Arc.

Devo trovare O


Ci sono vincoli sulle posizioni relative dei 3 punti? Immagine costa orientale, il punto medio è l'est più lontano. La tua soluzione non funzionerebbe in quanto le perpendicolari non converrebbero in mare aperto. Sono sicuro che possiamo inventare altri casi difficili!
mkennedy,

Mi chiedo se potresti usare una proiezione a distanza e far partire il calcolo da lì? progonos.com/furuti/MapProj/Normal/CartProp/DistPres/… Non sono sicuro dell'algoritmo per farlo, ce ne deve essere uno ... forse è il baricentro: en.wikipedia.org/wiki/Barycentric_coordinate_system
Alex Leith

Per soluzioni a un problema strettamente correlato, cerca nel nostro sito "trilateration" . Inoltre, gis.stackexchange.com/questions/10332/… è un duplicato ma non ha risposte adeguate (molto probabilmente perché la domanda è stata posta in modo confuso).
whuber

@mkennedy Non ci sono, in linea di principio, casi negativi, solo quelli numericamente instabili. Questi si verificano quando i tre punti base sono collineari; le due soluzioni (su un modello sferico) si presentano ai due poli del geodetico comune; in un modello ellissoidale si verificano vicino a dove ci si aspetterebbe quei poli.
whuber

L'uso dei loxodromi qui non sarebbe corretto: non sono le bisettrici perpendicolari. Sulla sfera, queste linee faranno parte di grandi cerchi (geodetica), ma sull'ellissoide si allontaneranno leggermente dall'essere geodetiche.
whuber

Risposte:


10

Questa risposta è divisa in più sezioni:

  • Analisi e riduzione del problema , mostrando come trovare il punto desiderato con routine "fisse".

  • Illustrazione: un prototipo funzionante , che fornisce un codice funzionante.

  • Esempio , che mostra esempi delle soluzioni.

  • Insidie , discutendo di potenziali problemi e come affrontarli.

  • Implementazione di ArcGIS , commenti sulla creazione di uno strumento ArcGIS personalizzato e su dove ottenere le routine necessarie.


Analisi e riduzione del problema

Cominciamo osservando che nel modello sferico (perfettamente rotondo) ci sarà sempre una soluzione , in realtà esattamente due soluzioni. Dati i punti base A, B e C, ciascuna coppia determina la sua "bisettrice perpendicolare", che è l'insieme di punti equidistanti dai due punti dati. Questa bisettrice è un geodetico (grande cerchio). La geometria sferica è ellittica : due geodetiche si intersecano (in due punti univoci). Pertanto, i punti di intersezione della bisettrice di AB e della bisettrice di BC sono - per definizione - equidistanti da A, B e C, risolvendo così il problema. (Vedi la prima figura in basso.)

Le cose sembrano più complicate su un ellissoide, ma poiché si tratta di una piccola perturbazione della sfera, possiamo aspettarci un comportamento simile. (L'analisi di questo ci porterebbe troppo lontano.) Le complicate formule utilizzate (internamente all'interno di un GIS) per calcolare distanze precise su un ellissoide non sono una complicazione concettuale, tuttavia: il problema è sostanzialmente lo stesso. Per vedere quanto sia semplice il problema, diciamolo in modo un po 'astratto. In questa affermazione, "d (U, V)" si riferisce alla distanza vera e precisa tra i punti U e V.

Dati tre punti A, B, C (come coppie lat-lon) su un ellissoide, trova un punto X per il quale (1) d (X, A) = d (X, B) = d (X, C) e ( 2) questa distanza comune è il più piccola possibile.

Queste tre distanze dipendono tutte dalla X sconosciuta . Pertanto, le differenze nelle distanze u (X) = d (X, A) - d (X, B) e v (X) = d (X, B) - d (X, C) sono funzioni con valore reale di X. Ancora una volta, in modo un po 'astratto, possiamo riunire queste differenze in una coppia ordinata. Useremo anche (lat, lon) come coordinate per X, permettendoci di considerarlo anche come una coppia ordinata, diciamo X = (phi, lambda). In questa configurazione, la funzione

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

è una funzione da una porzione di uno spazio bidimensionale che assume valori nello spazio bidimensionale e il nostro problema si riduce a

Trova tutti i possibili (phi, lambda) per i quali F (phi, lambda) = (0,0).

Qui è dove l'astrazione paga: esiste un sacco di ottimo software per risolvere questo problema (ricerca di radici multidimensionali puramente numeriche). Il modo in cui funziona è che si scrive una routine per calcolare F , quindi lo si passa al software insieme a tutte le informazioni sulle restrizioni sul suo input ( phi deve trovarsi tra -90 e 90 gradi e lambda deve essere tra -180 e 180 gradi). Si avvia per una frazione di secondo e restituisce (in genere) solo un valore di ( phi , lambda ), se riesce a trovarne uno.

Ci sono dettagli da gestire, perché c'è un'arte in questo: ci sono vari metodi di soluzione tra cui scegliere, a seconda di come " F " si comporta; aiuta a "guidare" il software fornendo un ragionevole punto di partenza per la sua ricerca (questo è un modo in cui possiamo ottenere la soluzione più vicina , piuttosto che qualsiasi altra); e di solito è necessario specificare la precisione della soluzione (in modo da sapere quando interrompere la ricerca). (Per ulteriori informazioni su ciò che gli analisti GIS devono sapere su tali dettagli, che emergono molto nei problemi GIS, visitare la sezione Consiglia argomenti da includere in un corso di Informatica per le tecnologie geospaziali e consultare la sezione "Miscellanea" verso la fine. )


Illustrazione: un prototipo funzionante

L'analisi mostra che dobbiamo programmare due cose: una stima iniziale approssimativa della soluzione e il calcolo di F stesso.

La stima iniziale può essere effettuata mediante una "media sferica" ​​dei tre punti base. Ciò si ottiene rappresentandoli in coordinate cartesiane geocentriche (x, y, z), calcolando la media di tali coordinate e proiettando la media sulla sfera e re-esprimendola in latitudine e longitudine. La dimensione della sfera è irrilevante e quindi i calcoli sono resi semplici: poiché questo è solo un punto di partenza, non abbiamo bisogno di calcoli ellissoidali.

Per questo prototipo funzionante ho usato 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
  ]

(La Ifcondizione finale verifica se la media potrebbe non riuscire a indicare chiaramente una longitudine; in tal caso, ricade in una media aritmetica diritta delle latitudini e delle lunghezze del suo input - forse non un'ottima scelta, ma almeno valida. Per coloro che usano questo codice come guida all'implementazione, nota che gli argomenti di Mathematica ArcTan sono invertiti rispetto alla maggior parte delle altre implementazioni: il suo primo argomento è la coordinata x, il secondo è la coordinata y e restituisce l'angolo creato dal vettore ( x, y).)

Per quanto riguarda la seconda parte, poiché Mathematica - come ArcGIS e quasi tutti gli altri GIS - contiene codice per calcolare distanze precise sull'ellissoide, non c'è quasi nulla da scrivere. Chiamiamo semplicemente la routine di ricerca radice:

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

L'aspetto più degno di nota di questa implementazione è il modo in cui evita la necessità di limitare la latitudine ( f) e la longitudine ( q) calcolandoli sempre rispettivamente a modulo 180 e 360 ​​gradi. Questo evita di dover limitare il problema (che spesso crea complicazioni). I parametri di controllo MaxIterationsecc. Sono ottimizzati per rendere questo codice la massima precisione possibile.

Per vederlo in azione, appliciamolo ai tre punti base indicati in una domanda correlata :

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

{-6.29692, 106.907}

Le distanze calcolate tra questa soluzione e i tre punti sono

{1450.23206979, 1450.23206979, 1450.23206978}

(questi sono metri). Sono d'accordo attraverso l'undicesima cifra significativa (che in realtà è troppo precisa, dal momento che le distanze sono raramente precise a un millimetro o giù di lì). Ecco una foto di questi tre punti (nero), le loro tre bisettrici reciproche e la soluzione (rosso):

Figura 1


Esempio

Per testare questa implementazione e comprendere meglio come si comporta il problema, ecco un diagramma di contorno della discrepanza quadrata media radice nelle distanze per tre punti base ampiamente distanziati. (La discrepanza RMS si ottiene calcolando tutte e tre le differenze d (X, A) -d (X, B), d (X, B) -d (X, C) e d (X, C) -d (X , A), calcolando la media dei loro quadrati e prendendo la radice quadrata: è uguale a zero quando X risolve il problema e altrimenti aumenta quando X si allontana da una soluzione, e quindi misura quanto siamo vicini a essere una soluzione in qualsiasi posizione. )

figura 2

I punti base (60, -120), (10, -40) e (45,10) sono mostrati in rosso in questa proiezione di Plate Carree; la soluzione (49.2644488, -49.9052992) - che ha richiesto 0,03 secondi per il calcolo - è in giallo. La sua discrepanza RMS è inferiore a tre nanometri , nonostante tutte le distanze rilevanti siano migliaia di chilometri. Le aree scure mostrano piccoli valori dell'RMS e le aree chiare mostrano valori elevati.

Questa mappa mostra chiaramente un'altra soluzione vicina (-49.2018206, 130,0297177) (calcolata su un RMS di due nanometri impostando il valore di ricerca iniziale diametralmente opposto alla prima soluzione).


insidie

Instabilità numerica

Quando i punti di base sono quasi collineari e vicini tra loro, tutte le soluzioni saranno a quasi mezzo mondo di distanza ed estremamente difficili da definire con precisione. Il motivo è che piccoli cambiamenti in una posizione in tutto il mondo - spostandolo verso o lontano dai punti di base - indurranno solo cambiamenti incredibilmente piccoli nelle differenze di distanze. Non c'è abbastanza accuratezza e precisione integrate nel normale calcolo delle distanze geodetiche per definire il risultato.

Ad esempio, iniziando con i punti base a (45.001, 0), (45, 0) e (44.999,0), che sono separati lungo il Meridiano Primo di soli 111 metri tra ogni coppia, triottiene la soluzione (11.8213, 77.745 ). Le distanze da esso ai punti base sono 8.127.964.998 77; 8.127.964.998 41; e 8.127.964.998 65 metri, rispettivamente. Sono d'accordo con il millimetro più vicino! Non sono sicuro di quanto accurato possa essere questo risultato, ma non sarei minimamente sorpreso se altre implementazioni restituissero posizioni lontane da questa, mostrando quasi una buona uguaglianza delle tre distanze.

Tempo di calcolo

Questi calcoli, poiché comportano una notevole ricerca utilizzando calcoli di distanza complicati, non sono veloci, di solito richiedono una notevole frazione di secondo. Le applicazioni in tempo reale devono esserne consapevoli.


Implementazione di ArcGIS

Python è l'ambiente di scripting preferito per ArcGIS (a partire dalla versione 9). Il pacchetto scipy.optimize ha un rootfinder multivariato rootche dovrebbe fare ciò che FindRootfa nel codice Mathematica . Naturalmente ArcGIS stesso offre calcoli accurati della distanza ellissoidale. Il resto, quindi, sono tutti i dettagli dell'implementazione: decidere come verranno ottenute le coordinate del punto base (da un livello? Digitato dall'utente? Da un file di testo? Dal mouse?) E come verrà presentato l'output (come coordinate visualizzato sullo schermo? come un punto grafico? come un nuovo oggetto punto in un livello?), scrivi quell'interfaccia, porta il codice Mathematica mostrato qui (semplice) e sarai pronto.


3
+1 Molto accurato. Penso che potresti dover iniziare a caricare per questo, @whuber.
Radar,

2
@Radar Grazie. Spero che la gente comprerà il mio libro quando (mai) alla fine appare :-).
whuber

1
Farà Bill ... Invia una bozza !!!

Eccellente! Tuttavia, sembra che una soluzione analitica sarebbe possibile. Riaffermando il problema nello spazio cartesiano 3d con 3 punti A, B, C ed E dove E è il centro della terra. Quindi trova due piani Piano1 e Piano2. Il piano 1 sarebbe il piano normale al piano ABE e passando attraverso E, punto medio (A, B). Allo stesso modo, Piano2 sarebbe il piano normale a pianoACE e passando attraverso E, punto medio (C, E). La lineaO formata dall'intersezione di Piano1 e Piano2 rappresenta punti equidistanti dai 3 punti. Il più vicino dei due punti ad A (o B o C) dove lineaO interseca la sfera è puntoO.
Kirk Kuykendall,

Quella soluzione analitica, @Kirk, si applica solo alla sfera. (Le intersezioni di piani con l'ellissoide non sono mai bisettrici perpendicolari nella metrica dell'ellissoide tranne che per alcuni ovvi casi eccezionali: quando sono meridiani o
equatori

3

Come notate, questo problema sorge nel determinare i confini marittimi; viene spesso definito problema "a tre punti" e puoi consultare Google e trovare diversi documenti che lo affrontano. Uno di questi articoli è da me (!) E offro una soluzione accurata e rapidamente convergente. Vedere la sezione 14 di http://arxiv.org/abs/1102.1215

Il metodo prevede i seguenti passaggi:

  1. indovina un punto triplo O
  2. usa O come centro di una proiezione equidistante azimutale
  3. progetto A, B, C, a questa proiezione
  4. trova il punto in questa proiezione, O '
  5. usa O 'come nuovo centro di proiezione
  6. ripetere fino a quando O 'e O coincidono

La formula necessaria per la soluzione a tre punti nella proiezione è riportata nel documento. Finché stai usando un'accurata proiezione equidistante azimutale, la risposta sarà esatta. La convergenza è quadratica, il che significa che sono necessarie solo poche iterazioni. Questo quasi sicuramente supererà i metodi generali di ricerca delle radici suggeriti da @whuber.

Non posso aiutarti direttamente con ArcGIS. Puoi prendere il mio pacchetto Python per fare calcoli geodetici da https://pypi.python.org/pypi/geographiclib e codificare la proiezione basata su questo è semplice.


modificare

Il problema di trovare il tri-point nel caso degenerato di @ whuber (45 + eps, 0) (45,0) (45-eps, 0) è stato considerato da Cayley in Sulle linee geodetiche su uno sferoide oblato , Phil. Mag. (1870), http://books.google.com/books?id=4XGIOoCMYYAC&pg=PA15

In questo caso, il punto triplo si ottiene seguendo un geodetico da (45,0) con azimut 90 e trovando il punto in cui la scala geodetica svanisce. Per l'ellissoide WGS84, questo punto è (-0.10690908732248, 89.89291072793167). La distanza da questo punto a ciascuno di (45.001,0), (45,0), (44.999) è 10010287.665788943 m (entro un nanometro o giù di lì). Si tratta di circa 1882 km in più rispetto alla stima di Whuber (che dimostra quanto sia instabile questo caso). Per una terra sferica, il punto triplo sarebbe (0,90) o (0, -90), ovviamente.

ADDENDUM: ecco un'implementazione del metodo equidistante azimutale usando 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

Provando questo usando Octave ottengo

ottava: formato 1> lungo
ottava: 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

come punto di partenza per New York, Tokyo e Wellington.

Questo metodo non è preciso per i punti colinear vicini, ad esempio [45.001,0], [45,0], [44.999,0]. In tal caso, dovresti risolvere per M 12 = 0 su un geodetico emanato da [45,0] in azimut 90. La seguente funzione fa il trucco (usando il metodo di 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

Per l'esempio, questo dà:

[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. Tuttavia, non è chiaro che un cercatore di radici generale funzionerà meno bene: la funzione si comporta bene vicino al suo ottimale e il metodo di Newton, per esempio, converge anche quadraticamente. ( Mathematica richiede in genere circa quattro passaggi per convergere; ogni passaggio richiede quattro valutazioni per stimare il giacobino.) Il vero vantaggio che vedo nel tuo metodo è che può essere facilmente copiato in un GIS senza ricorrere all'uso di un cercatore di radici.
whuber

Sono d'accordo. Il mio metodo è equivalente a Newton e quindi, a differenza del metodo di ricerca della radice di Mathematica, non è necessario stimare il gradiente prendendo le differenze.
Cffk,

Giusto - ma devi fare la riproiezione ogni volta, il che sembra circa la stessa quantità di lavoro. Apprezzo tuttavia la semplicità e l'eleganza del tuo approccio: è immediatamente ovvio che dovrebbe funzionare e convergere rapidamente.
whuber

Ho pubblicato i risultati per gli stessi punti di prova nella mia risposta.
Kirk Kuykendall,

3

Ero curioso di vedere quanto velocemente l'approccio di @ cffk converge su una soluzione, quindi ho scritto un test usando arcobjects, che ha prodotto questo risultato. Le distanze sono in metri:

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

Ecco il codice sorgente. (Modifica) Modificato FindCircleCenter per gestire le intersezioni (punti centrali) che cadono dal bordo della proiezione azimutale:

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

C'è anche un approccio alternativo nel numero di giugno 2013 di MSDN Magazine, Amoeba Method Optimization utilizzando C # .


modificare

In alcuni casi il codice precedentemente pubblicato è stato convertito in antipode. Ho modificato il codice in modo che produca questo output per i punti di test di @ cffk.

Ecco l'output che ora produce:

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

Ecco il codice rivisto:

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

modificare

Ecco i risultati che ottengo con 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

Questa è una convergenza incredibilmente veloce! (+1)
whuber

Dovresti usare una proiezione equidistante azimutale dall'onestà al bene centrata su newCenter. Invece stai usando la proiezione centrata sul polo N e stai spostando l'origine su newCenter. Può quindi essere casuale che tu ottenga una soluzione decente in questo caso (forse perché i punti sono vicini tra loro?). Sarebbe bello provarlo con 3 punti distanti migliaia di km. Un'implementazione della proiezione equidistante azimutale è data in mathworks.com/matlabcentral/fileexchange/…
cffk

@cffk L'unico altro modo in cui vedo per creare una proiezione equidistante azimutale centrata su un punto particolare è utilizzare lo stesso metodo ma con esriSRProjCS_World_AzimuthalEquidistant invece di esriSRProjCS_WGS1984N_PoleAziEqui (o esriSRProjCSSGS84SSGS84S84) L'unica differenza è che è centrato su 0,0 invece di 0,90 (o 0, -90). Potete guidarmi nell'esecuzione di un test con la matematica per vedere se questo produce risultati diversi da una proiezione "onestà alla bontà"?
Kirk Kuykendall l'

@KirkKuykendall: vedi l'addendum alla mia prima risposta.
Cffk,

1
@KirkKuykendall Quindi forse l'ESRI ha implementato una proiezione "onestà alla bontà"? La proprietà chiave richiesta per far funzionare questo algoritmo è che le distanze misurate dal "punto centrale" siano vere. E questa proprietà è abbastanza facile da controllare. (La proprietà azimutale relativa al punto centrale è secondaria e può influire solo sul tasso di convergenza.)
cffk
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.