홈으로 돌아가기

위경도 좌표를 기상청 날씨 격자(Grid)로 변환하는 수학적 원리와 HereWeather 구현

모바일 앱이나 날씨 웹 서비스에서 현재 위치 기반 날씨 정보를 조회할 때, 스마트폰의 GPS 수신으로 획득한 위도(Latitude)와 경도(Longitude) 값이 그대로 API 통신에 사용되지 않습니다. 대한민국 기상청(KMA)은 예보 데이터를 격자망 형태로 연산하여 행정구역 동네 단위로 서비스하기 때문에, 한반도를 약 5km 크기의 바둑판 격자(Grid) 좌표계로 구조화해 관리합니다. 위도 37.5665도, 경도 126.9780도 같은 경위도 좌표는 어떻게 기상청 격자 좌표인 X=60, Y=127 같은 정수로 변환될까요? 그 수학적 비밀은 바로 **'람베르트 정각원추도법'**에 있습니다.

1. 왜 기상청은 평면 격자(Grid)를 고집할까?

지구는 둥근 구체(3차원 타원체)이지만, 우리가 데이터를 시각화하거나 컴퓨터 수치 모델로 대기 상태를 모의 계산할 때는 2차원의 평면이 효율적입니다. 경위도와 같은 각도 기준 좌표를 그대로 쓰면 극지방으로 향할수록 바둑판 한 칸의 실제 거리가 극도로 왜곡되는 현상이 나타납니다. 이를 극복하기 위해 대한민국 영토 영역에서 왜곡률이 가장 낮게 설계된 원추(원뿔) 형태의 가상 면적에 좌표를 투사한 뒤 이를 균일한 격자망(nx, ny)으로 전환하는 것입니다. 기상청 격자 지도의 투영 기준점 원점은 한반도 내륙의 중심 부근(격자 기준 nx=60, ny=127)을 기준으로 설정되어 있습니다.

2. 투영법의 정수: 람베르트 정각원추도법

기상청 날씨 격자 지도 좌표계가 차용하고 있는 모델은 **람베르트 정각원추도법(Lambert Conformal Conic Projection)**입니다. 지구의 회전타원체 위에 가상의 원뿔을 씌우고 지표면의 좌표들을 이 원뿔의 측면에 투사하여 2차원 지도로 펴내는 기하학적 제작법입니다. 특히 2개의 표준위도선을 설정하여, 지표와 만나는 이 위도 영역의 투영 왜곡률을 0으로 억제하므로 중위도 지역에 길게 뻗어 있는 한반도 기상 분석에 오차가 가장 적다는 강력한 장점을 가집니다. 기상청 변환을 위한 기준 매개변수들은 다음과 같습니다.

  • 지구 가상 반경 (Re): 6371.00877 km
  • 격자 간격 (Grid Spacing): 5.0 km
  • 원점 격자 좌표 오프셋: X=43, Y=136
  • 투영 중심 경도 (OLON): 126.0도
  • 투영 중심 위도 (OLAT): 38.0도
  • 제1표준위도 (SLAT1): 30.0도
  • 제2표준위도 (SLAT2): 60.0도

3. 삼각함수로 구현하는 기상청 격자 변환 JS 소스코드

지구의 타원체 극좌표계를 평면 격자로 전환하는 공식은 꽤나 복잡한 삼각함수 및 자연로그 연산을 수반합니다. HereWeather와 같은 실시간 기상 애플리케이션은 사용자의 브라우저 Geolocation API를 통해 경위도 좌표를 획득한 후 아래의 연산을 즉시 클라이언트 사이드에서 실행합니다.

// 위경도 ➔ 기상청 격자(nx, ny) 양방향 변환 자바스크립트 함수
function dfs_xy_conv(code, v1, v2) {
    const RE = 6371.00877; // 지구 반경(km)
    const GRID = 5.0;      // 격자 간격(km)
    const SLAT1 = 30.0;    // 투영 위도1(degree)
    const SLAT2 = 60.0;    // 투영 위도2(degree)
    const OLON = 126.0;    // 기준점 경도(degree)
    const OLAT = 38.0;     // 기준점 위도(degree)
    const XO = 43;         // 기준점 X좌표(GRID)
    const YO = 136;        // 기준점 Y좌표(GRID)

    const DEGRAD = Math.PI / 180.0;
    const RADDEG = 180.0 / Math.PI;

    const re = RE / GRID;
    const slat1 = SLAT1 * DEGRAD;
    const slat2 = SLAT2 * DEGRAD;
    const olon = OLON * DEGRAD;
    const olat = OLAT * DEGRAD;

    let sn = Math.tan(Math.PI * 0.25 + slat2 * 0.5) / Math.tan(Math.PI * 0.25 + slat1 * 0.5);
    sn = Math.log(Math.cos(slat1) / Math.cos(slat2)) / Math.log(sn);
    let sf = Math.tan(Math.PI * 0.25 + slat1 * 0.5);
    sf = Math.pow(sf, sn) * Math.cos(slat1) / sn;
    let ro = Math.tan(Math.PI * 0.25 + olat * 0.5);
    ro = re * sf / Math.pow(ro, sn);
    let rs = {};

    if (code === "toXY") {
        rs['lat'] = v1;
        rs['lng'] = v2;
        let ra = Math.tan(Math.PI * 0.25 + (v1) * DEGRAD * 0.5);
        ra = re * sf / Math.pow(ra, sn);
        let theta = v2 * DEGRAD - olon;
        if (theta > Math.PI) theta -= 2.0 * Math.PI;
        if (theta < -Math.PI) theta += 2.0 * Math.PI;
        theta *= sn;
        rs['x'] = Math.floor(ra * Math.sin(theta) + XO + 0.5);
        rs['y'] = Math.floor(ro - ra * Math.cos(theta) + YO + 0.5);
    } else {
        rs['x'] = v1;
        rs['y'] = v2;
        let xn = v1 - XO;
        let yn = ro - v2 + YO;
        let ra = Math.sqrt(xn * xn + yn * yn);
        if (sn < 0.0) ra = -ra;
        let alat = Math.pow((re * sf / ra), (1.0 / sn));
        alat = 2.0 * Math.atan(alat) - Math.PI * 0.5;

        let theta = 0.0;
        if (Math.abs(xn) <= 0.0) {
            theta = 0.0;
        } else {
            if (Math.abs(yn) <= 0.0) {
                theta = Math.PI * 0.5;
                if (xn < 0.0) theta = -theta;
            } else {
                theta = Math.atan2(xn, yn);
            }
        }
        let alon = theta / sn + olon;
        rs['lat'] = alat * RADDEG;
        rs['lng'] = alon * RADDEG;
    }
    return rs;
}

4. HereWeather에서의 실전 응용 메커니즘

**HereWeather (여기날씨)** 서비스는 사용자가 페이지에 진입하면 다음의 워크플로우를 거쳐 정확한 국지성 정보를 제공합니다.

  1. 행정동 & 법정동 조회: GPS 좌표를 얻는 즉시 웹 역지오코딩 API를 활용해 현재 위치의 실시간 법정 행정동 주소 명칭(예: 서울시 강남구 개포4동)을 파악해 화면 상단에 띄워줍니다.
  2. 초국지 격자 변환: 얻은 위경도 정보를 상기 람베르트 수식을 통해 격자 (X, Y)로 실시간 변환 연산합니다.
  3. 기상청 단기예보 Open API 연동: 변환된 X, Y 좌표 정보를 헤더에 실어 기상청 초단기실황, 초단기예보 및 단기예보 API를 호출합니다.
  4. 데이터 가공 및 렌더링: 수신된 날씨 정보로부터 단순 온도뿐 아니라 **체감 기온, 최고/최저 기온, 강수 확률, 상대 습도, 풍속, 미세먼지(PM10)/초미세먼지(PM2.5) 대기질 등급, 그리고 일출/일몰 시각**과 24시간 시간별 기상 상태, 7일~10일 단위 주간 예보 정보까지 컴팩트하고 유려한 카드 레이아웃에 즉각 렌더링하여 제공합니다.

When weather apps query local forecast datasets based on location, GPS coordinates (Latitude/Longitude) are not used directly. The Korea Meteorological Administration (KMA) breaks down the Korean peninsula into a standard 5km x 5km flat Grid system. So how does GPS coordinates translate into integers like X=60, Y=127? The answer lies in the map projection mathematics of the **Lambert Conformal Conic Projection**.

1. Why the KMA Uses a Grid

The earth is a 3D sphere, while maps are 2D flats. Calculating climate physics using degrees/angles directly causes grid shapes to distort heavily as you move away from the equator. To minimize computing complexity, mid-latitude areas like Korea are projected onto a flat plane with constant grid sizes. The origin center of this grid map points to the Seoul Metropolitan area (nx=60, ny=127).

2. Lambert Conformal Conic Projection

The core algorithm is based on the **Lambert Conformal Conic Projection**. It places a virtual cone over the earth sphere and projects coordinates onto its surface. By selecting two 'Standard Parallel' lines, size and angle distortion are minimized for regions along mid-latitude boundaries. The conversion parameters are:

  • Earth radius (Re): 6371.00877 km
  • Grid spacing: 5.0 km
  • Grid origin offsets: X=43, Y=136
  • Origin longitude (OLON): 126.0 degrees
  • Origin latitude (OLAT): 38.0 degrees
  • 1st parallel (SLAT1): 30.0 degrees
  • 2nd parallel (SLAT2): 60.0 degrees

3. Trigonometric KMA Coordinate Conversion Code

Below is the standard JavaScript implementation of KMA Lambert projection to convert GPS lat/lon to Grid X/Y coordinates:

// GPS Lat/Lon <=> KMA Grid XY double-directional converter
function dfs_xy_conv(code, v1, v2) {
    const RE = 6371.00877;
    const GRID = 5.0;
    const SLAT1 = 30.0;
    const SLAT2 = 60.0;
    const OLON = 126.0;
    const OLAT = 38.0;
    const XO = 43;
    const YO = 136;

    const DEGRAD = Math.PI / 180.0;
    const RADDEG = 180.0 / Math.PI;

    const re = RE / GRID;
    const slat1 = SLAT1 * DEGRAD;
    const slat2 = SLAT2 * DEGRAD;
    const olon = OLON * DEGRAD;
    const olat = OLAT * DEGRAD;

    let sn = Math.tan(Math.PI * 0.25 + slat2 * 0.5) / Math.tan(Math.PI * 0.25 + slat1 * 0.5);
    sn = Math.log(Math.cos(slat1) / Math.cos(slat2)) / Math.log(sn);
    let sf = Math.tan(Math.PI * 0.25 + slat1 * 0.5);
    sf = Math.pow(sf, sn) * Math.cos(slat1) / sn;
    let ro = Math.tan(Math.PI * 0.25 + olat * 0.5);
    ro = re * sf / Math.pow(ro, sn);
    let rs = {};

    if (code === "toXY") {
        rs['lat'] = v1;
        rs['lng'] = v2;
        let ra = Math.tan(Math.PI * 0.25 + (v1) * DEGRAD * 0.5);
        ra = re * sf / Math.pow(ra, sn);
        let theta = v2 * DEGRAD - olon;
        if (theta > Math.PI) theta -= 2.0 * Math.PI;
        if (theta < -Math.PI) theta += 2.0 * Math.PI;
        theta *= sn;
        rs['x'] = Math.floor(ra * Math.sin(theta) + XO + 0.5);
        rs['y'] = Math.floor(ro - ra * Math.cos(theta) + YO + 0.5);
    } else {
        rs['x'] = v1;
        rs['y'] = v2;
        let xn = v1 - XO;
        let yn = ro - v2 + YO;
        let ra = Math.sqrt(xn * xn + yn * yn);
        if (sn < 0.0) ra = -ra;
        let alat = Math.pow((re * sf / ra), (1.0 / sn));
        alat = 2.0 * Math.atan(alat) - Math.PI * 0.5;

        let theta = 0.0;
        if (Math.abs(xn) <= 0.0) {
            theta = 0.0;
        } else {
            if (Math.abs(yn) <= 0.0) {
                theta = Math.PI * 0.5;
                if (xn < 0.0) theta = -theta;
            } else {
                theta = Math.atan2(xn, yn);
            }
        }
        let alon = theta / sn + olon;
        rs['lat'] = alat * RADDEG;
        rs['lng'] = alon * RADDEG;
    }
    return rs;
}

4. Implementation Workflow of HereWeather

**HereWeather** provides precise forecast services using this system:

  1. District Geocoding: Uses reverse geocoding to resolve GPS coords to administrative address (e.g. Gaepo 4-dong, Seoul) displayed at the header.
  2. Grid Mapping: Feeds the location values to the Lambert equations to yield KMA XY indices.
  3. Forecast Open API Query: Pulls real-time feeds from KMA Short-term forecast endpoints.
  4. Data Visualization: Renders weather metrics like **perceived temperature, precipitation chance, relative humidity, wind speed, micro-dust air quality levels, sunset/sunrise times, hourly graphs, and weekly forecasts** into clean interactive cards.