2009-07-14 13 views
25

Ho un codice C# che genera google maps. Questo codice analizza tutti i punti che devo tracciare sulla mappa e quindi elabora i limiti di un rettangolo per includerli. Quindi passa questo limite all'API di Google Maps per impostare il livello di zoom in modo appropriato per mostrare tutti i punti sulla mappa.Come trovo il lat/long che è x km a nord di un dato lat/long?

Questo codice funziona correttamente ma ho un nuovo requisito.

Uno dei punti può avere una precisione associata ad esso. Se questo è il caso, disegno un cerchio attorno al punto con il raggio impostato sul valore di precisione. Anche questo funziona bene, ma il mio controllo dei limiti ora non sta facendo quello che voglio che faccia. Voglio che il riquadro di delimitazione includa il cerchio completo.

Ciò richiede un algoritmo per prendere un punto x e calcolare il punto y che sarebbe z metri a nord di xe anche z metri a sud di x.

Qualcuno ha questo algoritmo, preferibilmente in C#. Ho trovato un algoritmo generico here ma a quanto pare non l'ho implementato correttamente poiché le risposte che sto ottenendo sono 1000 di km alla deriva.

Questo è l'esempio generico

Lat/lon given radial and distance 

A point {lat,lon} is a distance d out on the tc radial from point 1 if: 

    lat=asin(sin(lat1)*cos(d)+cos(lat1)*sin(d)*cos(tc)) 
    IF (cos(lat)=0) 
     lon=lon1  // endpoint a pole 
    ELSE 
     lon=mod(lon1-asin(sin(tc)*sin(d)/cos(lat))+pi,2*pi)-pi 
    ENDIF 

E questo è il mio C# traduzione.

// Extend a Point North/South by the specified distance 
    public static Point ExtendPoint(Point _pt, int _distance, int _bearing) 
    { 
     Decimal lat = 0.0; 
     Decimal lng = 0.0; 

     lat = Math.Asin(Math.Sin(_pt.Lat) * Math.Cos(_distance) + Math.Cos(_pt.Lat) * 
      Math.Sin(_distance) * Math.Cos(_bearing)); 

     if (Math.Cos(lat) == 0) 
     { 
      lng = _pt.Lng;  // endpoint a pole 
     } 
     else 
     { 
      lng = (
       (_pt.Lng - Math.Asin(Math.Sin(_bearing) * Math.Sin(_distance)/Math.Cos(lat)) 
       + Math.PI) % (2 * Math.PI)) - Math.PI; 
     } 

     ret = new Point(lat,lng); 
     return ret; 
    } 

io chiamo questa funzione con un cuscinetto da 0 a calcolare la nuova posizione a nord e un valore di 180 per calcolare la nuova posizione sud.

Qualcuno può vedere cosa ho fatto di sbagliato o forse fornire un algoritmo di lavoro noto?

risposta

6

Se si dispone di una certa latitudine e longitudine si può calcolare la latitudine e la longitudine corretta di un cambiamento x-km in latitudine in questo modo:

new-lat = ((old-km-north + x-km-change)/40,075) * 360) 
     ^is the ratio of the    ^times the ratio of the circle 
      of the earth the change    by 360 to get the total ratio 
      covers.        covered in degrees. 

Lo stesso si applica alla longitudine. Se hai la distanza totale più la modifica, puoi calcolare i gradi totali in modo simile.

new-long = ((old-km-east + x-km-change)/40,075) * 360) 
     ^is the ratio of the    ^times the ratio of the circle 
      of the earth the change    by 360 to get the total ratio 
      covers.        covered in degrees. 

Ancora una volta, questi calcoli dovrebbero funzionare, ma sto correndo fuori pura intuizione qui, ma la logica sembra tenere vero.

Edit: Come indicato da Skizz 40,075 deve essere adeguato alla circonferenza della terra in qualsiasi latitudine usando 2.pi.r.cos (LAT) o 40074.cos (LAT)

+0

Bene che sembra molto più semplice :-) Grazie. Ci proverò ora. Qual è il numero magico 40.075. È quel numero di secondi (gradi) che copre un KM? –

+0

Questa è la circonferenza della terra, dagli un colpo però. Questo è fuori intuizione quindi potrebbe essere necessario un aggiustamento. – Sam152

+0

40,075 è la circonferenza equatoriale della Terra. – KevDog

0

E ' più preciso se prima lo riproietta su UTM e poi controlla la distanza.

Spero che questo aiuti

20

Ho un codice molto simile. Mi ha ottenuto risultati molto vicini rispetto ad un'altra implementazione.

Penso che il problema con il tuo sia che stai usando "distanza" come distanza lineare in metri invece di distanza angolare in radianti.

/// <summary> 
/// Calculates the end-point from a given source at a given range (meters) and bearing (degrees). 
/// This methods uses simple geometry equations to calculate the end-point. 
/// </summary> 
/// <param name="source">Point of origin</param> 
/// <param name="range">Range in meters</param> 
/// <param name="bearing">Bearing in degrees</param> 
/// <returns>End-point from the source given the desired range and bearing.</returns> 
public static LatLonAlt CalculateDerivedPosition(LatLonAlt source, double range, double bearing) 
{ 
    double latA = source.Latitude * UnitConstants.DegreesToRadians; 
    double lonA = source.Longitude * UnitConstants.DegreesToRadians; 
    double angularDistance = range/GeospatialConstants.EarthRadius; 
    double trueCourse = bearing * UnitConstants.DegreesToRadians; 

    double lat = Math.Asin(
     Math.Sin(latA) * Math.Cos(angularDistance) + 
     Math.Cos(latA) * Math.Sin(angularDistance) * Math.Cos(trueCourse)); 

    double dlon = Math.Atan2(
     Math.Sin(trueCourse) * Math.Sin(angularDistance) * Math.Cos(latA), 
     Math.Cos(angularDistance) - Math.Sin(latA) * Math.Sin(lat)); 

    double lon = ((lonA + dlon + Math.PI) % UnitConstants.TwoPi) - Math.PI; 

    return new LatLonAlt(
     lat * UnitConstants.RadiansToDegrees, 
     lon * UnitConstants.RadiansToDegrees, 
     source.Altitude); 
} 

Dove

public const double EarthRadius = 6378137.0; // WGS-84 ellipsoid parameters 

e LatLonAlt è in gradi/m (conversione avviene internamente). Regolare secondo necessità.

presumo si può capire quale sia il valore per UnitConstants.DegreesToRadians è :)

+0

Sto provando questo approccio n java ma non funziona. Sto ottenendo valore di dlon = 0. Questo è il mio codice per dlon: double dlon = Math.atan2 (Math.sin (brng) * Math.sin (dist) * Math.cos (lat1), Math.cos (dist) -Math.sin (LAT1) * Math.sin (Lat2)); Sono già convertito il valore di latitudine e longitudine in radianti. Ogni codice è scritto come hai detto tu. –

+0

Abhendra Singh hai capito? So che è un vecchio post: D –

+0

Grazie! Ottimo metodo !!! ha funzionato per me – breceivemail

7

io non sono sicuro se mi manca qualcosa qui, ma credo che la questione potrebbe essere riformulato come, "Ho un lat/lon point, e voglio trovare il punto x metri a nord e x metri a sud di quel punto. "

Se questa è la domanda, non è necessario trovare una nuova longitudine (che semplifica le cose), è sufficiente una nuova latitudine. Un grado di latitudine è lungo circa 60 miglia nautiche in qualsiasi parte della Terra e un miglio nautico è di 1.852 metri. Così, per le nuove latitudini x metri a nord ea sud:

north_lat = lat + x/(1852 * 60) 
north_lat = min(north_lat, 90) 

south_lat = lat - x/(1852 * 60) 
south_lat = max(south_lat, -90) 

Questo non è del tutto preciso, perché la Terra non è una sfera perfetta con esattamente 60 miglia nautiche tra ogni grado di latitudine. Tuttavia, le altre risposte presuppongono che le linee di latitudine siano equidistanti, quindi presumo che non ti interessi. Se siete interessati in quanto errore che potrebbe introdurre, c'è un bel tavolo su Wikipedia che mostra "distanza di superficie per 1 ° cambio di latitudine" per le diverse latitudini a questo link:

http://en.wikipedia.org/wiki/Latitude#Degree_length

4

ci sono problemi con le due equazioni su Ed William's rather awesome site ... ma non li ho analizzati per capire perché.

Una terza equazione che I found here sembra dare risultati corretti.

Ecco il test case in php ... la terza equazione è corretta, i primi due danno valori errati per longitudine.

<?php 
      $lon1 = -108.553412; $lat1 = 35.467155; $linDistance = .5; $bearing = 170; 
      $lon1 = deg2rad($lon1); $lat1 = deg2rad($lat1); 
      $distance = $linDistance/6371; // convert dist to angular distance in radians 
      $bearing = deg2rad($bearing); 

      echo "lon1: " . rad2deg($lon1) . " lat1: " . rad2deg($lat1) . "<br>\n"; 

// doesn't work 
      $lat2 = asin(sin($lat1) * cos($distance) + cos($lat1) * sin($distance) * cos($bearing)); 
      $dlon = atan2(sin($bearing) * sin($distance) * cos($lat1), cos($distance) - sin($lat1) * sin($lat2)); 
      $lon2 = (($lon1 - $dlon + M_PI) % (2 * M_PI)) - M_PI; // normalise to -180...+180 

      echo "lon2: " . rad2deg($lon2) . " lat2: " . rad2deg($lat2) . "<br>\n"; 

// same results as above 
      $lat3 = asin((sin($lat1) * cos($distance)) + (cos($lat1) * sin($distance) * cos($bearing))); 
      $lon3 = (($lon1 - (asin(sin($bearing) * sin($distance)/cos($lat3))) + M_PI) % (2 * M_PI)) - M_PI; 

      echo "lon3: " . rad2deg($lon3) . " lat3: " . rad2deg($lat3) . "<br>\n"; 

// gives correct answer... go figure 
      $lat4 = asin(sin($lat1) * cos($linDistance/6371) + cos($lat1) * sin($linDistance/6371) * cos($bearing)); 
      $lon4 = $lon1 + atan2((sin($bearing) * sin($linDistance/6371) * cos($lat1)), (cos($linDistance/6371) - sin($lat1) * sin($lat2))); 

      echo "lon4: " . rad2deg($lon4) . " lat4: " . rad2deg($lat4) . "<br>\n"; 
?> 

nota ho ricevuto per e-mail da parte dell'autore (Ed Williams) delle prime due equazioni:

Dai miei "appunti di attuazione":

Nota sulla funzione mod. Questo sembra essere implementato in modo diverso in lingue diverse, con convenzioni diverse sul fatto che il segno del risultato segua il segno del divisore o del dividendo. (Vogliamo che il segno segua il divisore o sia Euclideo. Il file fmod di C e% di Java non funzionano.) In questo documento, Mod (y, x) è il resto dividendo y per x e sempre si trova nell'intervallo 0 < = mod < x. Per esempio: mod (. 2.3,2) = 0,3 e mod (. -2.3,2) = 1,7

Se si dispone di una funzione di piano (int in Excel), che restituisce floor (x) = "più grande intero inferiore o uguale a x "ad es floor (-2.3) = - 3 e floor (2.3) = 2

mod(y,x) = y - x*floor(y/x) 

Il seguente dovrebbe funzionare in assenza di un pavimento FUNCTION- indipendentemente dal fatto "int" tronca o round ribasso:

mod=y - x * int(y/x) 
if (mod < 0) mod = mod + x 

php è come fmod in C e lo fa "male" per i miei scopi.

+0

+1 il tuo link è fantastico grazie –

8

per i più pigri, (come me;)) una soluzione di copia-incolla, la versione di Erich Mirabal, con leggerissime modifiche:

using System.Device.Location; // add reference to System.Device.dll 
public static class GeoUtils 
{ 
    /// <summary> 
    /// Calculates the end-point from a given source at a given range (meters) and bearing (degrees). 
    /// This methods uses simple geometry equations to calculate the end-point. 
    /// </summary> 
    /// <param name="source">Point of origin</param> 
    /// <param name="range">Range in meters</param> 
    /// <param name="bearing">Bearing in degrees</param> 
    /// <returns>End-point from the source given the desired range and bearing.</returns> 
    public static GeoCoordinate CalculateDerivedPosition(this GeoCoordinate source, double range, double bearing) 
    { 
     var latA = source.Latitude * DegreesToRadians; 
     var lonA = source.Longitude * DegreesToRadians; 
     var angularDistance = range/EarthRadius; 
     var trueCourse = bearing * DegreesToRadians; 

     var lat = Math.Asin(
      Math.Sin(latA) * Math.Cos(angularDistance) + 
      Math.Cos(latA) * Math.Sin(angularDistance) * Math.Cos(trueCourse)); 

     var dlon = Math.Atan2(
      Math.Sin(trueCourse) * Math.Sin(angularDistance) * Math.Cos(latA), 
      Math.Cos(angularDistance) - Math.Sin(latA) * Math.Sin(lat)); 

     var lon = ((lonA + dlon + Math.PI) % (Math.PI*2)) - Math.PI; 

     return new GeoCoordinate(
      lat * RadiansToDegrees, 
      lon * RadiansToDegrees, 
      source.Altitude); 
    } 

    private const double DegreesToRadians = Math.PI/180.0; 
    private const double RadiansToDegrees = 180.0/ Math.PI; 
    private const double EarthRadius = 6378137.0; 
} 

Usage:

[TestClass] 
public class CalculateDerivedPositionUnitTest 
{ 
    [TestMethod] 
    public void OneDegreeSquareAtEquator() 
    { 
     var center = new GeoCoordinate(0, 0); 
     var radius = 111320; 
     var southBound = center.CalculateDerivedPosition(radius, -180); 
     var westBound = center.CalculateDerivedPosition(radius, -90); 
     var eastBound = center.CalculateDerivedPosition(radius, 90); 
     var northBound = center.CalculateDerivedPosition(radius, 0); 

     Console.Write($"leftBottom: {southBound.Latitude} , {westBound.Longitude} rightTop: {northBound.Latitude} , {eastBound.Longitude}"); 
    } 
} 
+0

Grazie mille, hai reso la mia giornata !! –

+0

mi sta dando risultati sbagliati in qualche modo. – usman

Problemi correlati