BLOG main image
Category (342)
MySpace (89)
Astronomy (50)
Development (178)
Drum (25)
linux에서 subversion설정
누리에 없을 자그마한 자국
살라딘의 생각
saladin's me2DAY
3D Avata - BuddyPoke
기찬 개발이야기
[FLEX] ANT로 ASDOC 사용하기
THLIFE.net
Flash10 대응 Textcube 1.7.5.1..
텍스트큐브 공지사항
«   2008년 11월   »
            1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30            
325658 Visitors up to today!
Today 158 hit, Yesterday 963 hit
/Astronomy/Programming 관련글 보기 2007년 07월 26일 14시 06분

지구에서 어느 두 지점의 경도(longitude)와 위도(latitude) 값을 알면 그 사이의 거리를 구할 수 있다?

말은 쉬워 보입니다. 하지만 여러분들은 이 공식을 유도해본 적 있나요? 그리 쉽지만은 않을 겁니다. 그러나 간단한 수학적 접근만 하면 충분히 유도할 수 있습니다. 이 공식을 구하기 위해선 좌표계 변환(coordinate transform)과 삼각함수(Trigonometrical function)를 이용하면 됩니다. 여기서는 직접 다음 소개될 공식을 유도하지 않습니다. 유도는 여러분께 맡기고요. 저는 이 유도된 공식을 쓸 수 있는 C와 C# 프로그램만 소개할까 합니다.

이해를 돕기위해 그림을 하나 그렸습니다. (무척 힘들었다. 무려 20분 걸렸음 ㅡㅡ;;)

사용자 삽입 이미지


자, 위의 그림은 지구입니다. 노란표시된 부분 위쪽이 우리나라이고 밑에는 호주입니다.(믿거나 말거나) 그럼 우리는 우리나라의 한 좌표의 경도, 위도값(lon1, lat1)과 호주의 한 좌표의 경도, 위도값(lon2, lat2)를 안다고 가정했을때 구면에서의 두 좌표의 거리 d를 구하는게 목표입니다.
잠깐 넘겨집고 간다면 경도(longitude)는 동경 0도~180도, 서경 0~180도 까지 범위를 가집니다. 동경일때는 +로 하고 서경일때는 -라고 생각합시다. 또 위도는 북위 0~90도, 남위 0~90도 까지가 범위입니다. 마찬가지로 북위는 +, 남위는 -라고 합시다. 이렇게 가정한 하에 프로그램을 제작할 것입니다.

자 그럼 공식을 소개하죠.

사용자 삽입 이미지

자 생각보다 어렵지 않습니다. 유도하는데 시간이 걸리겠지만요. 여기서 한가지 가정은 지구가 정확한 구가 아니라는 겁니다. 그러나 잘 알다시피 지구는 표면도 거칠 뿐 아니라 정확한 구가 아닙니다. 적도 쪽으로 약간 더 찌그러진 타원체이지요. 아주 정확한 거리를 계산하려면 이 방법을 쓰면 안됩니다. 타원체일때를 고려할려면 다른 공식을 계산해서 써야겠지요. 그건 나중에 소개하고요.(언제? ㅋ)

그럼 저 공식을 이용해서 만든 C#코드를 공개하겠습니다. 참고로 이 코드는 Codeproject.com에서 주어왔어요. ^^

C# 코드 (Language : cpp)
using System;
using System.Text;

public class CDistanceBetweenLocations
{
    public static double Calc(double Lat1,
        double Long1, double Lat2, double Long2)
    {
        /*
            The Haversine formula according to Dr. Math.
            http://mathforum.org/library/drmath/view/51879.html

            dlon = lon2 - lon1
            dlat = lat2 - lat1
            a = (sin(dlat/2))^2 + cos(lat1) * cos(lat2) * (sin(dlon/2))^2
            c = 2 * atan2(sqrt(a), sqrt(1-a))
            d = R * c

            Where
                * dlon is the change in longitude
                * dlat is the change in latitude
                * c is the great circle distance in Radians.
                * R is the radius of a spherical Earth.
                * The locations of the two points in
                    spherical coordinates (longitude and
                    latitude) are lon1,lat1 and lon2, lat2.
        */

        double dDistance = Double.MinValue;
        double dLat1InRad = Lat1 * (Math.PI / 180.0);
        double dLong1InRad = Long1 * (Math.PI / 180.0);
        double dLat2InRad = Lat2 * (Math.PI / 180.0);
        double dLong2InRad = Long2 * (Math.PI / 180.0);

        double dLongitude = dLong2InRad - dLong1InRad;
        double dLatitude = dLat2InRad - dLat1InRad;

        // Intermediate result a.
        double a = Math.Pow(Math.Sin(dLatitude / 2.0), 2.0) +
            Math.Cos(dLat1InRad) * Math.Cos(dLat2InRad) *
            Math.Pow(Math.Sin(dLongitude / 2.0), 2.0);

        // Intermediate result c (great circle distance in Radians).
        double c = 2.0 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1.0 - a));

        // Distance.
        // const Double kEarthRadiusMiles = 3956.0;
        const Double kEarthRadiusKms = 6376.5;
        dDistance = kEarthRadiusKms * c;

        return dDistance;
    }

    public static double Calc(string NS1, double Lat1, double Lat1Min,
        string EW1, double Long1, double Long1Min, string NS2,
        double Lat2, double Lat2Min, string EW2,
        double Long2, double Long2Min)
    {
        double NS1Sign = NS1.ToUpper() == "N" ? 1.0 : -1.0;
        double EW1Sign = NS1.ToUpper() == "E" ? 1.0 : -1.0;
        double NS2Sign = NS2.ToUpper() == "N" ? 1.0 : -1.0;
        double EW2Sign = EW2.ToUpper() == "E" ? 1.0 : -1.0;
        return (Calc(
            (Lat1 + (Lat1Min / 60)) * NS1Sign,
            (Long1 + (Long1Min / 60)) * EW1Sign,
            (Lat2 + (Lat2Min / 60)) * NS2Sign,
            (Long2 + (Long2Min / 60)) * EW2Sign
            ));
    }

    public static void Main(string[] args)
    {
        if (args.Length < 12)
        {
            System.Console.WriteLine("usage: DistanceBetweenLocations" +
                " N 43 35.500 W 80 27.800 N 43 35.925 W 80 28.318");
            return
        }
        System.Console.WriteLine(Calc(
            args[0],
            System.Double.Parse(args[1]),
            System.Double.Parse(args[2]),
            args[3],
            System.Double.Parse(args[4]),
            System.Double.Parse(args[5]),
            args[6],
            System.Double.Parse(args[7]),
            System.Double.Parse(args[8]),
            args[9],
            System.Double.Parse(args[10]),
            System.Double.Parse(args[11])));

    }

}
 


어떠세요? 감이 오시나요?  다음은 위의 코드를 제가 C언어로 수정한 겁니다.

c 코드 (Language : c)
#include <stdio.h>
#include <math.h>

#define PI 3.14159265358979323846264338327950288419

double Calc(double Lat1, double Long1, double Lat2, double Long2)
{
/*
The Haversine formula according to Dr. Math.
http://mathforum.org/library/drmath/view/51879.html

dlon = lon2 - lon1
dlat = lat2 - lat1
a = (sin(dlat/2))^2 + cos(lat1) * cos(lat2) * (sin(dlon/2))^2
c = 2 * atan2(sqrt(a), sqrt(1-a))
d = R * c

Where
* dlon is the change in longitude
* dlat is the change in latitude
* c is the great circle distance in Radians.
* R is the radius of a spherical Earth.
* The locations of the two points in
spherical coordinates (longitude and
latitude) are lon1,lat1 and lon2, lat2.
*/

    double dDistance;
    const double kEarthRadiusKms = 6376.5;
    double dLat1InRad = Lat1 * (PI / 180.0);
    double dLong1InRad = Long1 * (PI / 180.0);
    double dLat2InRad = Lat2 * (PI / 180.0);
    double dLong2InRad = Long2 * (PI / 180.0);

    double dLongitude = dLong2InRad - dLong1InRad;
    double dLatitude = dLat2InRad - dLat1InRad;

    // Intermediate result a.
        double a = pow(sin(dLatitude / 2.0), 2.0) +
        cos(dLat1InRad) * cos(dLat2InRad) *
        pow(sin(dLongitude / 2.0), 2.0);

    // Intermediate result c (great circle distance in Radians).
    double c = 2.0 * atan2(sqrt(a), sqrt(1.0 - a));

    // Distance.
    dDistance = kEarthRadiusKms * c;

    return dDistance;
}

double Calc(char NS1, double Lat1, double Lat1Min,
    char EW1, double Long1, double Long1Min, char NS2,
    double Lat2, double Lat2Min, char EW2,
    double Long2, double Long2Min)
{
    double NS1Sign = (NS1 == 'N') ? 1.0 : -1.0;
    double EW1Sign = (NS1 == 'E') ? 1.0 : -1.0;
    double NS2Sign = (NS2 == 'N') ? 1.0 : -1.0;
    double EW2Sign = (EW2 == 'E') ? 1.0 : -1.0;
    return (Calc(
        (Lat1 + (Lat1Min / 60)) * NS1Sign,
        (Long1 + (Long1Min / 60)) * EW1Sign,
        (Lat2 + (Lat2Min / 60)) * NS2Sign,
        (Long2 + (Long2Min / 60)) * EW2Sign
        ));
}
int main()
{
    //N 43 35.500 W 80 27.800 N 43 35.925 W 80 28.318
    printf("Distance = %lf km \n",
        Calc('N',43, 35.500, //Lat1
            'W', 80, 27.800, //Lon1
            'N', 43, 35.925, //Lat2
            'W', 80, 28.318)); //Lon2

    return 0;
}
 

자, 이게 전부입니다.

이 글은 제가 천문노트(http://astronote.org )에 이미 올린 글입니다.

참고글 : http://blog.jidolstar.com/217 (Flex로 만든 프로그램입니다.)

글쓴이 : 지돌스타(http://blog.jidolstar.com, http://astronote.org)

이 글의 관련글
Trackback Address :: http://blog.jidolstar.com/trackback/172
Tracked from SecuPrint 개발자 모임 | 2007년 08월 27일 12시 46분 | DEL
void GetCartesian(double Lat, double Long, double *x, double *y, double *z) { /* 유사구면좌표계를 직교좌표계로 변환. http://ko.wikipedia.org/wiki/%EA%B5%AC%EB%A9%B4_%EC%A2%8C%ED%91%9C%EA%B3%84 */ *x = cos(Lat)*cos(Long); *y = cos(Lat)*sin(Long); *z = sin(Lat); } double..
BlogIcon 지돌스타 | 2007년 08월 29일 00시 01분 | PERMALINK | EDIT/DEL | REPLY
(위에 트랙백 걸어주신분께...)
트랙백 달아주셔서 감사합니다.
제가 올린 식은 범용적인 것으로 완전한 구에서 임의의 지점에 대한 거리입니다.
즉, 일반공식이라는 것입니다. 다시말해 제가 올린 식은 최단거리뿐 아니라 최장거리에도 동일하게 적용됩니다.
올리신 분께서 벡터내적을 이용해 acos만 사용한다면 말그대로 180 이하의 거리일때만 구할 수 있는것이지요. ^^
혹시 제 글과 비교해서 이 공식을 이해를 못하고 쓰실분이 계실까봐 설명을 덧붙혔습니다. ^^
BlogIcon daewonyoon | 2007년 08월 29일 10시 40분 | PERMALINK | EDIT/DEL
일반공식이라는 말이 이해가 잘 안가서요. "최장거리"란 게 어떻게 정의되는 건가요.

구 위에서 최장거리는 (최단거리와 마찬가지로) 어차피 두 점을 지나는 대원을 거치는 호의 길이일 것 같습니다. 둘 중에 긴쪽 호의 길이일텐데요.

공식에 주어지는 결국 두 점의 순서(즉, 델타의 부호)에 따라 도출되는 값이 (짧은 호)거나 (긴 호)가 나와야 할 것 같은데, 공식에서 보면 경도와 위도 둘의 델타의 부호는 계산과정에서 없어져서 맞지 않습니다.

제가 뭘 착각하고 있나요?
BlogIcon 지돌스타 | 2007년 08월 31일 11시 23분 | PERMALINK | EDIT/DEL
앗... 제가 오히려 뭔가 착각을 한것 같군요.
180도가 넘어가는 지점에 대해서는 생각할 필요가 없다는 것을 깨달았습니다. 그렇다고 한다면 대원님께서 하신 방법이 맞습니다. 대원님께서 훨씬 직관적인 코드를 제시해주셨네요. ^^
다른분이 들어왔을때 내용에 대한 오해소지가 있으므로 위에 댓글은 삭제했습니다.
제 블로그에 제시된 코드는 벡터를 이용하지 않고 계산한 것이기 때문에 오히려 수식이 복잡하게 보이는군요. 결과는 대원님것과 같을겁니다.
BlogIcon daewonyoon | 2007년 08월 31일 13시 06분 | PERMALINK | EDIT/DEL | REPLY
제가 제시한 코드에 큰 약점이 있습니다. P1 = P2 일 때 반환되는 거리가 (매우 작은 값이겠지만) 정확하게 0을 주지 않겠네요. 지돌스타님꺼는 정확하게 0을 줄겁니다. 지돌스타님이 제시한 공식도 제가 쓴 {좌표변환+내적} 수식을 삼각함수 합차공식 아크로바틱을 잘 하면 나올 것 같네요. 덕분에 하버사인이란 용어도 알았어요.
BlogIcon 지돌스타 | 2007년 08월 31일 13시 33분 | PERMALINK | EDIT/DEL
네 그렇군요.
http://blog.jidolstar.com/217 에 프로그램을 만들어보았습니다.
덕분에 제가 많이 배웠습니다.
Name
Password
Homepage
Secret