안녕하세요. 호랑인 입니다.


이번에는 별지도를 어떻게 투영할지에 대해 포스팅하려 합니다.


우선, 가장 먼저 Star 라는 class 를 정의해 줍시다.


    public class Star {

        public final String StarName;
        public final String StarID;
        public final double StarMag;
        public final double StarDeclination;
        public final double StarRightAscension;

        public Star(String starName, String starID, double starMagnitude, double starRightAscension, double starDeclination) {
            StarName = starName;
            StarID = starID;
            StarMag = starMagnitude;
            StarDeclination = starDeclination;
            StarRightAscension = starRightAscension;
        }
    }



이제 이 Star 의 class 조건에 맞춰서 다양한 별을 추가해줍시다.


이는 텍스트 파일 등에 이 데이터를 저장해 놓고 불러오는 방법이 있는데요, 어차피 그렇게 많은 수의 별을 넣을 것이 아니라, 육안으로 잘 보이는 별들만 넣을 것이기 때문에, 저는 아주 쉽게 하드 코딩해서 넣었습니다.


코드가 때문에 매우 길어 보일 수 있으나, 실제로 코딩할 때에는 왼쪽 바에 있는 - 모양 버튼으로 이 줄들은 숨길 수 있습니다.

혹시나 어떤 데이터를 넣었는지 궁금하신 분들을 위해 코드를 공개하겠습니다. 매우 길기 때문에 원치 않으시는 분들은 그냥 넘어가시기 바랍니다.




이제 별들을 그려봅시다.


자세한 것은 여기에 나와 있듯이, draw() 함수를 사용해서 그림을 그릴 수 있습니다. 여기에 Paint object 를 만들어 흰색 점을 찍을 것입니다.


    @Override
    public void draw(Canvas canvas){
        super.draw(canvas);
        if(canvas != null){

            canvas.drawColor(Color.WHITE);
            Paint sky_paint = new Paint();
            sky_paint.setColor(Color.rgb(0,0,0));

            Paint star_paint = new Paint();
            star_paint.setColor(Color.rgb(250, 250, 250));

            Paint focus_star = new Paint();
            focus_star.setColor(Color.rgb(250,0,0));

            canvas.drawCircle((float)VIEW_WIDTH/2, (float)VIEW_HEIGHT/2, (float)VIEW_WIDTH/2 - 16, sky_paint);

            if (!("null".equals(this.star_name))){
                canvas.drawText(star_name, 32, 16, sky_paint);
            }

            for (int i=0; i<MAX_STAR_NUM; i++){
                float x = star_2D_map[0][i];
                float y = star_2D_map[1][i];
                MainActivity.Star star = star_list.get(i);
                double starMag = star.StarMag;
                if (x > 0 && x < VIEW_WIDTH && y > 0 && y < VIEW_HEIGHT){
                    // Draw Star
                    float radius = (float) ( 5*Math.pow(10, (-starMag/5)) );
                    if (i == star_num){
                        canvas.drawCircle(x, y, radius, focus_star);
                    }else {
                        canvas.drawCircle(x, y, radius, star_paint);
                    }
                }
            }
        }
    }


제일 먼저 검정색 하늘을 그립니다. sky_paint 라는 변수 이름을 갖고 있습니다. 그 뒤에 흰색 star_paint 들을 그려줍니다. focus_star 는 나중에 검색한 별들은 빨간색으로 그리고 싶을 때 사용할 예정입니다.


if 문에 들어가기 전에 있는 drawCirle 함수는 검정색 하늘을 그려줍니다.


이제 for 문으로 들어가면, 진짜 별들을 그리게 됩니다. 보시면 star_2D_map 이라는 list 에서 x와 y 값을 가져오고, radius 를 별의 등급을 사용해서 그립니다. radius 는 실제로 우리 눈에 보이는 밝기를 별의 안시등급으로부터 환산하고, 그 밝기가 보이는 별의 크기(면적)에 비례한다는 사실을 사용하여 계산했습니다. 그러면 결국 남은 과정은 star_2D_map 이라는 list 를 만드는 과정이 됩니다. 어떻게 구에 있는 별들을 투영시켜 그릴 수 있을까요?


다양한 방법이 있겠지만, 저는 우선 모든 별들을 (0,0,0) 을 중심으로 하는 반지름 1의 원 위에 위치시켰습니다. 현재, 북극은 (0,0,1) 입니다.


    public void Star_to_xyz(){
        for (int i=0; i<star_list.size();i++){

            MainActivity.Star star = star_list.get(i);
            star_coord[0][i] = Math.sin(Math.toRadians(90-star.StarDeclination)) * Math.cos(Math.toRadians(star.StarRightAscension));
            star_coord[1][i] = Math.sin(Math.toRadians(90-star.StarDeclination)) * Math.sin(Math.toRadians(star.StarRightAscension));
            star_coord[2][i] = Math.cos(Math.toRadians(90-star.StarDeclination));
        }
    }



그리고 간단한 sin cos 계산으로 위도를 맞췄습니다.


    public void Star_set_north_pole(){
        for (int i=0; i<star_list.size();i++) {
            double x_temp = star_coord[0][i];
            double z_temp = star_coord[2][i];

            double x = x_temp * Math.cos(NORTH_POLE) + z_temp * Math.sin(NORTH_POLE);
            double z = -x_temp * Math.sin(NORTH_POLE) + z_temp * Math.cos(NORTH_POLE);

            star_coord[0][i] = x;
            star_coord[2][i] = z;
        }
    }



그리고 단순히 사영시켰습니다.


    public void Star_xyz_to_2D(){
        for(int i=0; i<MAX_STAR_NUM; i++){
            if (star_coord[0][i] <= Math.sin(Math.toRadians(30))){
                star_2D_map[0][i] = -1;
                star_2D_map[1][i] = -1;
            }
            else{
                double x = star_coord[0][i];
                double expand_ratio = 1 / x;
                double y = star_coord[1][i];
                double z = star_coord[2][i];
                star_2D_map[0][i] = (float)(y * expand_ratio * expand + VIEW_WIDTH/2);
                star_2D_map[1][i] = (float)(-z * expand_ratio * expand + VIEW_HEIGHT/2);
            }
        }
    }



여기에서 일부 천문학도들은 "이건 말도 안된다. 어떻게 사영시킨 것이 제대로 된 별지도라 할 수 있느냐, 왜곡이 너무 심할 것이다" 라 하실 수 있습니다. 물론 이해합니다. 이를 사영할 수 있는 방법은 정말 다양하지만, 지금 이 앱의 목적과는 상관이 깊지않다 생각하여 이렇게 하기로 했습니다.


일부는 "어차피 그냥 사영시킬 것이면 왜 굳이 어렵게 반지름 1의 구에 위치시키고 계산을 했냐" 고 물을 것입니다. 그 이유는 지금부터 설명드리겠습니다.




자 이제, 지도를 만들었으니, 두 가지 기능을 넣어 생명을 줍시다.


1. 땅 아래에 있는 별은 나타나서는 안된다.

2-1. 하늘이 시간에 따라 바뀌는 것을 보정해주고 싶다.

2-2. 하늘을 내 마음대로 돌려보고 싶다.


저는 2번 중에 2-2를 선택했습니다.


만약 하늘이 돌아야 한다면, 어떻게 계산해야할까요? 이를 편하게 하기 위해 반지름 1의 구에 위치시켰던 것입니다.


    public void Star_turn_Right(){
        double angle = Math.toRadians(- zero_angle);
        for (int i=0; i<star_list.size();i++) {
            double x_temp = star_coord[0][i];
            double y_temp = star_coord[1][i];

            double x = x_temp * Math.cos(angle) - y_temp * Math.sin(angle);
            double y = x_temp * Math.sin(angle) + y_temp * Math.cos(angle);

            star_coord[0][i] = x;
            star_coord[1][i] = y;
        }
    }



이제 화면을 누르면 Star_turn_Right() 를 실행하여 별들의 위치를 시간의 흐름에 따라 돌릴 수 있습니다.






완성된 별지도의 모습입니다. 잘 보시면 오리온 자리가 아랫쪽에 보이고 있습니다. 제일 위에 있는 별이 북극성이고, 화면을 누르면 하늘이 돌지만, 북극성은 거의 같은 위치를 유지합니다. (북극성이 조금씩 움직일 텐데요, 실제로 북극성은 정 북극에 있지 않아 움직입니다.)


이렇게 별지도를 다 그렸습니다. 다음에도 더 유익한 내용으로 돌아오겠습니다. 감사합니다.

+ Recent posts