지난 번에는 임의의 정해진 위치에서의 날씨 정보를 얻는 방법을 완료하였다. 하지만 본래의 목적인 챗봇에 적용하기 위해서는 정해진 위치 뿐만 아니라 이용자가 원하는 위치의 날씨도 필요하다. 이번에는 임의의 주어진 주소로 부터 날씨 정보를 얻을 수 있도록 수정해보도록 한다.

원하는 임의의 위치의 날씨 정보 요청하기

원하는 임의의 위치의 날씨 정보를 얻기 위해서는 기상청 api 의 request parameter 중 nx, ny 값을 알아야 한다.

이 값은 엑셀파일에서 확인할 수 있지만 엑셀과 같은 형태에서 내가 원하는 지역의 격자 값을 검색하여 얻는 것은 어려운 일이다.

행정구역 상의 어떤 단위의 명칭이 주어질지 먼저 구분(시, 군, 구, 읍, 면, 리 등), 행정구역상에 어떻게 저장되어있는지 어떤 것이 정확하게 맞는지 확인(예를 들어 '북구'라고 검색했을 때 '경상북도 포항시북구'가 맞는지, '울산광역시 북구'가 맞는지, '충청남도 천안시서북구'가 맞는지 구분) 등 어려움이 많고,

이를 방지하기 위해 정확한 행정구역을 입력해야만 올바른 정답을 준다면, 챗봇을 구현한다는 최종 목적에서 너무 멀어지는 꼴이 되어버려 어떻게 구현해야 할지 고민했다.

결론적으로 지도상의 위도, 경도 정보를 이용하여 x, y 격자를 계산할 수 있는 식이 있다는 것을 확인, 문자열이 주어질 경우 geopy 라이브러리를 이용하여 해당 위치의 위도/경도 값을 얻고 이를 x, y 격자로 변환하여 기상청api로 날씨 정보를 얻기로 하였다.

아래 링크의 reference 부분을 보면 위도/경도를 통해 xy 그리드를 얻고, 그 반대로도 계산할 수 있는 공식이 주어져있다.

https://fronteer.kr/service/kmaxy

 

기상청 격자정보 - 위경도 변환 : Grid XY - Lat, Lon

데이터형식 : 위도, 경도 37.579871128849334, 126.98935225645432 35.101148844565955, 129.02478725562108 33.500946412305076, 126.54663058817043

fronteer.kr

# python_geocoding.py

import math

NX = 149  ## X축 격자점 수
NY = 253  ## Y축 격자점 수

Re = 6371.00877  ##  지도반경
grid = 5.0  ##  격자간격 (km)
slat1 = 30.0  ##  표준위도 1
slat2 = 60.0  ##  표준위도 2
olon = 126.0  ##  기준점 경도
olat = 38.0  ##  기준점 위도
xo = 210 / grid  ##  기준점 X좌표
yo = 675 / grid  ##  기준점 Y좌표
first = 0

if first == 0:
    PI = math.asin(1.0) * 2.0
    DEGRAD = PI / 180.0
    RADDEG = 180.0 / PI

    re = Re / grid
    slat1 = slat1 * DEGRAD
    slat2 = slat2 * DEGRAD
    olon = olon * DEGRAD
    olat = olat * DEGRAD

    sn = math.tan(PI * 0.25 + slat2 * 0.5) / math.tan(PI * 0.25 + slat1 * 0.5)
    sn = math.log(math.cos(slat1) / math.cos(slat2)) / math.log(sn)
    sf = math.tan(PI * 0.25 + slat1 * 0.5)
    sf = math.pow(sf, sn) * math.cos(slat1) / sn
    ro = math.tan(PI * 0.25 + olat * 0.5)
    ro = re * sf / math.pow(ro, sn)
    first = 1


def mapToGrid(lat, lon, code=0):
    ra = math.tan(PI * 0.25 + lat * DEGRAD * 0.5)
    ra = re * sf / pow(ra, sn)
    theta = lon * DEGRAD - olon
    if theta > PI:
        theta -= 2.0 * PI
    if theta < -PI:
        theta += 2.0 * PI
    theta *= sn
    x = (ra * math.sin(theta)) + xo
    y = (ro - ra * math.cos(theta)) + yo
    x = int(x + 1.5)
    y = int(y + 1.5)
    return x, y


def gridToMap(x, y, code=1):
    x = x - 1
    y = y - 1
    xn = x - xo
    yn = ro - y + yo
    ra = math.sqrt(xn * xn + yn * yn)
    if sn < 0.0:
        ra = -ra
    alat = math.pow((re * sf / ra), (1.0 / sn))
    alat = 2.0 * math.atan(alat) - PI * 0.5
    if math.fabs(xn) <= 0.0:
        theta = 0.0
    else:
        if math.fabs(yn) <= 0.0:
            theta = PI * 0.5
            if xn < 0.0:
                theta = -theta
        else:
            theta = math.atan2(xn, yn)
    alon = theta / sn + olon
    lat = alat * RADDEG
    lon = alon * RADDEG

    return lat, lon

이에 필요한 위도와 경도는 아래의 코드를 통해 구한다.

# python_geocoding.py

from geopy.geocoders import Nominatim

# name으로 주어진 주소 혹은 명칭에 해당하는 위도/경도, 주소값 리턴
def geocoding(name):
    app = Nominatim(user_agent="South Korea")
    location = app.geocode(name)
    return location.latitude, location.longitude

원하는 주소, 혹은 명칭이 name으로 주어진다면, geopy로 위도/경도 정보를 얻고, 이를 공식을 통해 x, y 그리드로 변환할 수 있다.

기상청 api 코드 수정하기

geocoding 함수와 mapToGrid 함수를 이용하여 x, y 를 구할 수 있다.

앞서 제작했던 기상청 api 파일에 이 x, y 값을 입력받을 수 있도록 약간의 수정을 해주면 주소를 입력하여 해당 지역의 초단기예보 날씨 정보를 리턴받는 함수로 수정해줄 수 있다.

# python_weather_api.py

# ... 중략

# 초단기예보 정보 요청
def get_ultra_srt_fcst(x, y):
    token = get_token()
    if not token:
        print("no token")
        return False

    # ... 중략

    params = "?" + urlencode(
        {
            quote_plus("serviceKey"): token,  # 인증키
            quote_plus("numOfRows"): "60",  # 한 페이지 결과 수 // default : 10
            quote_plus("pageNo"): "1",  # 페이지 번호 // default : 1
            quote_plus("dataType"): "JSON",  # 응답자료형식 : XML, JSON
            quote_plus("base_date"): base_date,  # 발표일자 // yyyymmdd
            quote_plus("base_time"): base_time,  # 발표시각 // HHMM, 매 시각 45분 이후 호출
            quote_plus("nx"): x,  # 예보지점 X 좌표
            quote_plus("ny"): y,  # 예보지점 Y 좌표
        }
    )
    res = requests.get(callback_url + unquote(params))
    items = res.json().get("response").get("body").get("items").get("item")

    # ... 하략

정리

이번에는 geopy 모듈을 이용하여 주소 혹은 장소 string 을 입력 주었을 때, 이 위치의 위도/경도를 얻었고, 이 위도/경도 값을 적절히 변환하여 기상청 api 에 필요한 x, y 그리드 값을 얻었다. 이 정보를 이용하여 날씨 정보를 얻었으니, 앞으로는 slack 을 이용한 챗봇 제작을 해보려고 한다.

차후 예정 작업

  1. 슬랙봇을 만들어 내가 원하는 메세지를 전송하기
  2. 특정 대화방에 메세지가 올라오면 해당 내용을 이벤트로 받기
  3. 해당 메세지 내용을 분석하여 날씨 요청에 대한 내용이라면 날씨 정보를 받아 해당 대화방에 업로드하기

geocoding 코드를 일부 수정하여 입력된 주소의 상위 주소를 출력하도록 하였다.

# python_geocoding.py

# ... 중략

# name으로 주어진 주소 혹은 명칭에 해당하는 위도/경도/주소 리턴
def geocoding(address):
    app = Nominatim(user_agent="South Korea")
    location = app.geocode(address)
    lat, lon = location.latitude, location.longitude
    location2 = app.reverse(f"{lat} {lon}")
    # 주어진 주소에서 필요한 주소 정보만 모아 주소 리턴
    data_list = ["province", "city", "county", "borough", "town"]
    ad = []
    for n in data_list:
        try:
            ad.append(location2.raw["address"][n])
        except:
            pass
    return location.latitude, location.longitude, " ".join(ad)


def xy_geocoding(address):
    lat, lon, ad = geocoding(address)
    x, y = mapToGrid(lat, lon)
    return x, y, ad

n = input("위치 입력: ")
print(xy_geocoding(n))

내용 관련하여 도움이나 조언 언제든 환영합니다!

반응형
  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기