web-dev-qa-db-ja.com

特定の緯度/経度のx km北にある緯度/経度を見つけるにはどうすればよいですか?

Googleマップを生成するC#コードがあります。このコードは、マップ上にプロットする必要があるすべてのポイントを調べ、長方形の境界を計算してそれらのポイントを含めます。次に、この境界をGoogle Maps APIに渡して、ズームレベルを適切に設定し、地図上のすべてのポイントを表示します。

このコードは正常に動作していますが、新しい要件があります。

ポイントの1つには、精度が関連付けられている場合があります。この場合は、半径を精度の値に設定して、ポイントの周りに円を描きます。再びこれはうまくいきますが、私の境界チェックは今私がやりたいことをしていません。バウンディングボックスに完全な円を含めたいです。

これには、点xを取り、xの北にzメートル、xの南にzメートルになる点yを計算するアルゴリズムが必要です。

誰かがこのアルゴリズムを、できればC#で持っていますか?私は一般的なアルゴリズム here を見つけましたが、得られた答えは数千キロのドリフトであるため、これを正しく実装していないようです。

これは一般的な例です

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

そして、これは私のC#翻訳です。

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

私はこの関数を方位0で呼び出して新しい北の位置を計算し、値180で新しい南の位置を計算しています。

誰かが私が間違ったことを見ることができるか、おそらく既知の動作アルゴリズムを提供できますか?

26
Steve Weet

特定の緯度と経度がある場合、次のように緯度のx-km変化の正しい緯度と経度を計算できます。

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.

同じことが経度にも当てはまります。合計距離と変化がある場合は、同様に合計角度を計算できます。

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.

繰り返しになりますが、これらの計算は機能するはずですが、ここでは純粋な直感を使い果たしていますが、ロジックは正しいようです。

編集:Skizz 40,075で指摘されているように、2.pi.r.cos(lat)または40074.cos(lat)を使用して、特定の緯度で地球の円周に調整する必要があります

5
Sam152

非常によく似たコードがあります。他の実装と比較すると、非常に近い結果が得られました。

あなたの問題は、ラジアン単位のangular距離ではなく、メートル単位の線形距離として「距離」を使用していることだと思います。

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

どこ

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

latLonAltは度/メートル単位です(変換は内部で行われます)。必要に応じて調整します。

私はあなたがUnitConstants.DegreesToRadiansの値が何であるかを理解できると思います:)

22
Erich Mirabal

怠惰な人々のために(私のように;))コピー&ペーストソリューション、非常に小さな変更を加えたErich Mirabalのバージョン:

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

使用法:

[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}");
    }
}
10
Zar Shardan

ここに何か足りないものがあるかどうかはわかりませんが、「緯度/経度のポイントがあり、そのポイントの北xメートル、南xメートルのポイントを見つけたいと思っています。 」

それが問題である場合、新しい経度を見つける必要はなく(これにより物事が簡単になります)、新しい緯度が必要になります。緯度は地球上のおよそ60海里であり、海里は1,852メートルです。したがって、新しい緯度xメートルの南北の場合:

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

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

地球は完全な球体ではないため、これは完全には正確ではありません。緯度と緯度の間がちょうど60海里であるからです。ただし、他の回答は緯度の線が等距離であると仮定しているので、私はあなたがそれを気にしないと仮定しています。発生する可能性のあるエラーの量に興味がある場合は、Wikipediaの「緯度の1°の変化あたりの表面距離」を示すリンクが、このリンクにあるさまざまな緯度で示されています。

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

7
ryan_s

Ed Williamのかなり素晴らしいサイト ...の2つの方程式には問題がありますが、それらを分析して理由を確認しませんでした。

ここで見つけた という3番目の方程式は、適切な結果を与えるようです。

これがphpのテストケースです... 3番目の方程式は正しく、最初の2つは経度の非常に誤った値を示しています。

<?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";
?>

注:最初の2つの方程式の著者(Ed Williams)からのメールを受け取りました:

私の「実装ノート」から:

Mod関数に注意してください。これは、結果の符号が除数または被除数の符号の後に続くかどうかについての規則が異なるため、言語によって実装が異なるようです。 (符号は除数に従うか、ユークリッドにする必要があります。CのfmodとJavaの%は機能しません。)このドキュメントでは、Mod(y、x)はyをxで除算した剰余であり、常に0 <=の範囲にあります。 mod <x。例:mod(2.3,2。)= 0.3およびmod(-2.3,2。)= 1.7

あなたがfloor関数(Excelのint)を持っている場合、それはfloor(x)= "x以下の最大の整数"を返します。 floor(-2.3)=-3およびfloor(2.3)= 2

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

以下は、 "int"が切り捨てられるか、切り捨てられるかに関係なく、floor関数がない場合に機能します。

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

phpはCのfmodのようなもので、私の目的では「間違っています」。

4
Sy Moen

最初に [〜#〜] utm [〜#〜] に再投影してから距離を確認すると、より正確になります。

お役に立てれば

0
Pablo Cabrera

それが価値があるもののために、私はPHPに例があります。これはOPが要求していることを行うことができます。私の例では、開始緯度/経度座標の周りにボックスを描画していますが、コードは簡単にできます。 Xポイント数kmまたはマイル離れた単一のポイントを取得するために使用されます。

http://www.richardpeacock.com/blog/2011/11/draw-box-around-coordinate-google-maps-based-miles-or-kilometers

0
Richard