[Objective-C] 特定の緯度経度と角度と距離を元に緯度経度を算出する(楕円あり)

現在地を起点として、特定距離の矩形を作成する。
みたいなことが必要だったので計算式を書いた(ほぼコピペ)。。。けど使われないことになった。

やってることとしては

ある地点を中心とした半径Xメートル同心円上における角度Aの地点の緯度経度を求めよ(ただし地球は楕円形として扱うこと)

みたいな感じでしょうか。文系な自分にはこういうのよくわからないです。。

結果として下記ソースでほぼイケるけど、浮動小数点数の演算ばかりなので誤差が気になるところ。メソッドのコメントに記載しているURLでJavascriptのソースがあったので、Objective-Cに書き直しただけって感じですが、一部変えています。詳細はソース見てもらえれば。

参考サイトではdo whileでループしているが

この部分の動作を見てみたら、Javascriptでは精度を上げるようなループをしていたけど期待通りに実装する方法がわからなかったので、ループの中身を1度実行するだけに変更しています。

#import <CoreLocation/CoreLocation.h>

#define EARTH           6378137.f
#define EARTH_FLAT      1.f / 298.257222101
#define EARTH_SHORT     EARTH * (1.f - EARTH_FLAT)

#define RADIANS_TO_DEGREES(radians) (radians * 180.f / M_PI)
#define DEGREES_TO_RADIANS(degrees) (degrees / 180.f * M_PI)

@implementation AppDelegate

/**
 @param point 起点となる緯度経度
 @param angle 角度、北を0度として時計回りに360度
 @param distance 距離(メートル)
 */
+ (CLLocationCoordinate2D)calcLocationWithPoint:(CLLocationCoordinate2D)point
                                          angle:(double)angle
                                       distance:(double)distance {
    /*
     http://tancro.e-central.tv/grandmaster/script/vincentyJS.html
     http://tancro.e-central.tv/grandmaster/excel/vincenty.html
     
     MKCoordinateRegionが使えるかと思ったけど
     MKMapRectが無いと期待している値が取れなかった
     */
    // 現在地の経度
    double latitude = DEGREES_TO_RADIANS(point.latitude);
    // 現在地の緯度
    double longitude = DEGREES_TO_RADIANS(point.longitude);
    // 算出する地点の角度
    double alpha12 = DEGREES_TO_RADIANS(angle);
    
    double U1 = atan((1 - EARTH_FLAT) * tan(latitude));
    double sigma1 = atan(tan(U1) / cos(alpha12));
    double alpha = asin(cos(U1) * sin(alpha12));
    double u2 = pow(cos(alpha), 2) * (pow(EARTH, 2) - pow(EARTH_SHORT, 2)) / pow(EARTH_SHORT, 2);
    double A = 1 + (u2 / 16384.f) * (4096.f + u2 * (-768 + u2 * (320.f - 175.f * u2)));
    double B = (u2 / 1024.f) * (256.f + u2 * (-128 + u2 * (74.f - 47.f * u2)));
    double sigma = distance / EARTH_SHORT / A;

    /*
     参考サイトではdo whileでループしているが
     iPhoneで試したところうまく動かなかったためループさせないよう修正
     */
    double dm2;
    dm2 = 2.f * sigma1 + sigma;
    double x1 = cos(sigma) * ( -1 + 2.f * pow(cos(dm2),2) ) - B / 6.f * cos(dm2) * ( -3.f + 4.f * pow(sin(dm2),2)) * ( -3.f + 4.f * pow(cos(dm2), 2));
    double dSigma = B * sin(sigma) * ( cos(dm2) + B / 4.f * x1);
    sigma = distance / EARTH_SHORT / A + dSigma;
    
    double x = sin(U1) * cos(sigma) + cos(U1) * sin(sigma) * cos(alpha12);
    double y = (1.f - EARTH_FLAT) * pow( pow( sin(alpha), 2) + pow(sin(U1) * sin(sigma) - cos(U1) * cos(sigma) * cos(alpha12), 2), .5);
    double lambda = atan(sin(sigma) * sin(alpha12) / (cos(U1) * cos(sigma) - sin(U1) * sin(sigma) * cos(alpha12)));
    double C = (EARTH_FLAT / 16.f) * pow(cos(alpha12), 2) * (4.f + EARTH_FLAT * (4.f - 3.f * pow(cos(alpha), 2)));
    double z = cos(dm2) + C * cos(sigma) * (-1 + 2.f * pow(cos(dm2), 2));
    double omega = lambda - (1.f - C) * EARTH_FLAT * sin(alpha) * (sigma + C * sin(sigma) * z);
    double lat = atan(x / y);
    double lng = longitude + omega;
    CLLocationCoordinate2D coordinate = CLLocationCoordinate2DMake(RADIANS_TO_DEGREES(lat),
                                                                   RADIANS_TO_DEGREES(lng));
    return coordinate;
}

# pragma mark - AppDelegate methods

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    // 東京駅付近の緯度経度
    CLLocationCoordinate2D basePoint = CLLocationCoordinate2DMake(35.681330, 139.766029);
    CLLocationCoordinate2D result = [[self class] calcLocationWithPoint:basePoint
                                                          angle:0.f
                                                       distance:100.f];
    NSLog(@"basePoint:  %f, %f", basePoint.latitude, basePoint.longitude);// basePoint:  35.681330, 139.766029
    NSLog(@"result:     %f, %f", result.latitude, result.longitude); // result:     35.682225, 139.766029

    return YES;
}


@end

結果

iOSでこういった計算を行うとき、MapKit.frameworkのMKMapViewを使用しているとMKRectが使えるのでラクになるけど、MapViewを作ってるのはちょっとなぁと思ったので計算式を書きましたという経緯です。

参考サイト