python flask를 이용해서 REST API를 구현하여 nugu speaker로부터 받은 post 요청을 처리하는 코드이다.
크게 Function part와 Flask part로 나눌 수 있다.
from flask import Flask, request, jsonify
from flask_restful import Resource, Api
import json
import os
import requests
from pyproj import Proj
from pyproj import transform
import datetime
import time
import pandas as pd
from pandas import DataFrame
import telepot
from apscheduler.schedulers.background import BackgroundScheduler
#Function part
def location(): #find users location
url = 'https://www.googleapis.com/geolocation/v1/geolocate?key=AIzaSyDkx6muQn1Jz-y6hLOcTPVdYAhklm6WJQo'
data = {
'considerIp': True,
#'homeMobileCountryCode': 450,
#'homeMobileNetworkCode': 5,
#'radioType':'gsm',
#'carrier': "SKTelecom",
#"wifiAccessPoints":[{'macAddress':'40:DC:9D:06:EC:CA'}]
}
result = requests.post(url, data)
a=result.json()
lat=a['location']['lat'] # Y point
lng=a['location']['lng'] # X point
return lat,lng
def trans(lat,lng): #wgs84 -> tm128
WGS84 = { 'proj':'latlong', 'datum':'WGS84', 'ellps':'WGS84', }
TM128 = { 'proj':'tmerc', 'lat_0':'38N', 'lon_0':'128E', 'ellps':'bessel',
'x_0':'400000', 'y_0':'600000', 'k':'0.9999',
'towgs84':'-146.43,507.89,681.46'}
def wgs84_to_tm128(longitude, latitude):
return transform( Proj(**WGS84), Proj(**TM128), longitude, latitude )
x_point,y_point=wgs84_to_tm128(lng,lat)
return x_point,y_point
def ask_oil_type(ans):
if ans == "2번" : #경유
return "D047"
elif ans == "1번" : #휘발유
return "B027"
else :
return None
def browse(x_point,y_point,oil_type):
url = 'http://www.opinet.co.kr/api/aroundAll.do'
payload = {
"code" : "F886201116",
"out" : "json",
"x" : x_point,
"y" : y_point,
"radius" : "1000",
"prodcd" : oil_type ,
"sort" : "1"
}
result = requests.get(url,params=payload).json()
return result
def content():
global data_num
global oil_list
global title
global cost
data_num = len(oil_list["RESULT"]["OIL"]) #주유소 개수 확인
if (data_num == 1):
for i in range(0, 1):
title.append(oil_list["RESULT"]["OIL"][i]['OS_NM'])
elif (data_num >= 3):
for i in range(0, 3):
title.append(oil_list["RESULT"]["OIL"][i]['OS_NM'])
elif (data_num == 2):
for i in range(0,2):
title.append(oil_list["RESULT"]["OIL"][i]['OS_NM'])
content_num = data_num
if content_num == 0: #검색된 주유소가 0개인 경우
title.append("null")
cost.append("null")
elif content_num > 3: #검색된 주유소 3개 초과일 경우 차례대로 3개만 처리
for i in range(0, 3):
cost.append(oil_list['RESULT']['OIL'][i]['PRICE'])
else: #검색된 주유소가 2개 혹은 3개인 경우
for i in range(content_num):
cost.append(oil_list['RESULT']['OIL'][i]['PRICE'])
return title, cost
def action(c):
global data_num
keys=['number','title','cost']
arr=[]
if data_num == 0:
return arr
elif data_num == 1:
values = ["1", c[0], c[1]]
A = dict(zip(keys, values))
arr.append(A)
return arr
elif data_num == 2:
values_1 = ["1", c[0][0], c[1][0]]
values_2 = ["2", c[0][1], c[1][1]]
A = dict(zip(keys, values_1))
B = dict(zip(keys, values_2))
arr.append(A)
arr.append(B)
return arr
else:
values_1 = ["1", c[0][0], c[1][0]]
values_2 = ["2", c[0][1], c[1][1]]
values_3 = ["3", c[0][2], c[1][2]]
A = dict(zip(keys, values_1))
B = dict(zip(keys, values_2))
C = dict(zip(keys, values_3))
arr.append(A)
arr.append(B)
arr.append(C)
return arr
def make_response(result_list):
global select
global oil_type
response = {
"version": "2.0",
"resultCode": "OK",
"output": {
"COUNT": "0",
"STATION_INFORMATION": "",
}
}
result_len = len(result_list)
temp = ""
if result_len == 0:
return response
elif result_len>0:
response["output"]["COUNT"] = str(result_len)
if result_len == 1:
temp = str(result_list[0]["title"]) + "," + str(result_list[0]["cost"]) + "원"
temp = temp.replace(']','')
temp = temp.replace('[','')
temp = temp.replace("'",'')
response["output"]["STATION_INFORMATION"] = temp
else:
for i in range(result_len):
temp = temp + str(result_list[i]["title"]) + "," + str(result_list[i]["cost"]) + "원" + ","
temp = temp.replace("]","")
temp =temp.replace("[","")
temp = temp.replace("'","")
response["output"]["STATION_INFORMATION"] = temp
return response
#flask part
app = Flask(__name__)
api = Api(app)
class Getparams(Resource):
def post(self):
data = request.get_json()
print(data)
global select
global oil_type
ans = ""
if 'SELECT' in data['action']['parameters'].keys():
select = data['action']['parameters']['SELECT']['value']
if select == "1번" or select == "2번":
ans = select
if 'OIL_TYPE' in data['action']['parameters'].keys():
oil_type = data['action']['parameters']['OIL_TYPE']['value']
if oil_type == "경유":
ans = "2번"
elif oil_type == "휘발유":
ans = "1번"
a,b = location()
a,b = trans(a,b)
global oil_list
oil_list = browse(a,b,ask_oil_type(ans))
print(oil_list)
global data_num
global title
global cost
title = []
cost = []
result = action(content())
response = make_response(result)
if 'SELECT' in data['action']['parameters'].keys():
response["output"]["SELECT"] = select
if 'OIL_TYPE' in data['action']['parameters'].keys():
response["output"]["OIL_TYPE"] = oil_type
print(response)
return jsonify(response)
api.add_resource(Getparams,'/answer.lowprice','/answer.lowprice.diesel','/answer.lowprice.gasoline','/answer.lowprice.diesel.0','/answer.lowprice.diesel.1','/answer.lowprice.gasoline.0','/answer.lowprice.gasoline.1','/answer.lowprice.select.diesel','/answer.lowprice.select.diesel0','/answer.lowprice.select.diesel1','/answer.lowprice.select.gasoline','/answer.lowprice.select.gasoline0','/answer.lowprice.select.gasoline1')
if __name__ == "__main__":
app.run()
url
에는 필요한 Google Geolocation API url과 api key를 입력해주고,data
에는 어떤 정보를 기반으로 위치추적을 요청할지에 대한 코드가 들어간다. nuguoil은 ip를 기반으로 사용자 위치를 추적하기 때문에 'considerIp': True
를 넣어주었다.
result 변수
에 geolocation api에게서 얻은 정보들이 들어오고, 그것을 json형식으로 변환하여 a 변수
에 넣어준다. 여기서 우리가 필요한 값은 a['location']['lat']
와 a['location']['lng']
이고 각각 y좌표, x좌표로 대응되는 값이다. 이 값들을 각각 lat변수
와 lng변수
에 넣어주고 return lat,lng
해준다.
def location(): #find users location
url = 'https://www.googleapis.com/geolocation/v1/geolocate?key=AIzaSyDkx6muQn1Jz-y6hLOcTPVdYAhklm6WJQo'
data = {
'considerIp': True,
#'homeMobileCountryCode': 450,
#'homeMobileNetworkCode': 5,
#'radioType':'gsm',
#'carrier': "SKTelecom",
#"wifiAccessPoints":[{'macAddress':'40:DC:9D:06:EC:CA'}]
}
result = requests.post(url, data)
a=result.json()
lat=a['location']['lat'] # Y point
lng=a['location']['lng'] # X point
return lat,lng
현재 geolocation을 이용한 사용자의 위치 추적이 불가능하다. 개인정보 문제로 nugu speaker측에서 승인을 받아야지만 사용할 수 있는 기능이기 때문이다. 우리는 실행을 위해 일단 임의의 좌표를 넣어서 코드를 완성하기로 했다. (flaks part 참조 )
우리는 좌표 변환에 관련된 함수를 포함하고있는 Pyproj 패키지를 이용했다. parameter로 받은 WGS84형식의 lat,lng 값을 TM128형식으로 변환하여 x_point
, y_point
변수에 담아 반환한 것이다.
def trans(lat,lng): #wgs84 -> tm128
WGS84 = { 'proj':'latlong', 'datum':'WGS84', 'ellps':'WGS84', }
TM128 = { 'proj':'tmerc', 'lat_0':'38N', 'lon_0':'128E', 'ellps':'bessel',
'x_0':'400000', 'y_0':'600000', 'k':'0.9999',
'towgs84':'-146.43,507.89,681.46'}
def wgs84_to_tm128(longitude, latitude):
return transform( Proj(**WGS84), Proj(**TM128), longitude, latitude )
x_point,y_point=wgs84_to_tm128(lng,lat)
return x_point,y_point
해당 함수는 nugu speaker에게서 post요청으로 받아온 정보를 인자로 받아서 오피넷 api를 사용 할 때 입력해야하는 "prodcd" 코드로 변환해주는 역할을 수행한다.
def ask_oil_type(ans):
if ans == "2번" : #경유
return "D047"
elif ans == "1번" : #휘발유
return "B027"
else :
return None
이 함수는 오피넷 api를 이용하여 x_point
와 y_point
, oil_type
을 인자로 받고 다시 request 인자로 넣어 해당하는 위치에서 반경 5km내에 있는 가장 저렴한 주유소 순으로 정렬한 주유소 정보를 가져온다.
def browse(x_point,y_point,oil_type):
url = 'http://www.opinet.co.kr/api/aroundAll.do'
payload = {
"code" : "F886201116",
"out" : "json",
"x" : x_point,
"y" : y_point,
"radius" : "5000",
"prodcd" : oil_type ,
"sort" : "1"
}
result = requests.get(url,params=payload).json()
return result
해당 함수는 앞에서 brows함수의 리턴값으로 나온 주유소 리스트가 저장 되어 있는 global 타입의 oil_list
에 몇개의 주유소가 있는지 카운트해서 global타입의 data_num변수
에 넣어준 후, 최대 3개의 주유소 이름과 가격을 각각 global 타입의 title
, cost
에 넣어준다.
def content():
global data_num
global oil_list
global title
global cost
data_num = len(oil_list["RESULT"]["OIL"]) #주유소 개수 확인
if (data_num == 1):
for i in range(0, 1):
title.append(oil_list["RESULT"]["OIL"][i]['OS_NM'])
elif (data_num >= 3):
for i in range(0, 3):
title.append(oil_list["RESULT"]["OIL"][i]['OS_NM'])
elif (data_num == 2):
for i in range(0,2):
title.append(oil_list["RESULT"]["OIL"][i]['OS_NM'])
content_num = data_num
if content_num == 0: #검색된 주유소가 0개인 경우
title.append("null")
cost.append("null")
elif content_num > 3: #검색된 주유소 3개 초과일 경우 차례대로 3개만 처리
for i in range(0, 3):
cost.append(oil_list['RESULT']['OIL'][i]['PRICE'])
else: #검색된 주유소가 2개 혹은 3개인 경우
for i in range(content_num):
cost.append(oil_list['RESULT']['OIL'][i]['PRICE'])
return title, cost
action(c) 함수는 앞에서 content함수의 리턴인 **title**
, **cost**
리스트를 c라는 인자로 받아서 **keys=['number','title','cost']**
를 키값으로 하는 딕셔너리에 c값을 넣어서 리턴하는 함수이다.
def action(c):
global data_num
keys=['number','title','cost']
arr=[]
if data_num == 0:
return arr
elif data_num == 1:
values = ["1", c[0], c[1]]
A = dict(zip(keys, values))
arr.append(A)
return arr
elif data_num == 2:
values_1 = ["1", c[0][0], c[1][0]]
values_2 = ["2", c[0][1], c[1][1]]
A = dict(zip(keys, values_1))
B = dict(zip(keys, values_2))
arr.append(A)
arr.append(B)
return arr
else:
values_1 = ["1", c[0][0], c[1][0]]
values_2 = ["2", c[0][1], c[1][1]]
values_3 = ["3", c[0][2], c[1][2]]
A = dict(zip(keys, values_1))
B = dict(zip(keys, values_2))
C = dict(zip(keys, values_3))
arr.append(A)
arr.append(B)
arr.append(C)
return arr
make_response(result_list) 함수는 action함수의 리턴값으로 나온 딕셔너리 리스트를 받아서, nugu speaker에서 post 요청을 받은 후 nugu speaker에서 필요로 하는 response block 데이터 형식에 맞춘 딕셔너리를 반환하는 함수이다. response["output"]["COUNT"]
에는 파싱한 주유소의 총 개수가 들어가며, response["output"]["STATION_INFORMATION"]
에는 파싱한 주유소의 정보가 들어간다. "~주유소, ~원, ~주유소, ~원"과 같이 최대 3개의 주유소의 정보가 나열된 형태의 문자열의 형식을 취한다.
"~주유소, ~원, ~주유소, ~원" 처럼 나열된 형식을 이용하는 이유는 추후에 nugu speaker 에서 주유소 정보에 대한 발화를 할 때, 간단한 방식으로 끊김 없이 정보를 말할 수 있도록 하기 위함이다.
def make_response(result_list):
global select
global oil_type
response = {
"version": "2.0",
"resultCode": "OK",
"output": {
"COUNT": "0",
"STATION_INFORMATION": "",
}
}
result_len = len(result_list)
temp = ""
if result_len == 0:
return response
elif result_len>0:
response["output"]["COUNT"] = str(result_len)
if result_len == 1:
temp = str(result_list[0]["title"]) + "," + str(result_list[0]["cost"]) + "원"
temp = temp.replace(']','')
temp = temp.replace('[','')
temp = temp.replace("'",'')
response["output"]["STATION_INFORMATION"] = temp
else:
for i in range(result_len):
temp = temp + str(result_list[i]["title"]) + "," + str(result_list[i]["cost"]) + "원" + ","
temp = temp.replace("]","")
temp =temp.replace("[","")
temp = temp.replace("'","")
response["output"]["STATION_INFORMATION"] = temp
return response
먼저 data = request.get_json()
를 통해 받아온 request body에는 서비스 사용자가 어떤 종류의 기름을 기준으로 최저가 주유소를 검색하고 싶어하는지와 관련된 정보가 담겨있다. 해당 정보는 SELECT
또는 OIL_TYPE
이라는 이름의 parameter에 담겨있으며 nugu playbuilder에서 설정한 action에 따라서 둘 중 하나의 parameter만 request body로 전달된다. 해당 key값이 있는지 if문의 조건을 통해 확인한 후 ans변수
에 value값을 넣어준다. browse함수에 oiltype을 인자로 넣을 때, 관련된 코드로 변환해주는 함수인 ask_oil_type(ans) 함수를 이용하기 위해서는 ans변수에 들어가는 값을 "1번", 혹은 "2번"으로 변환을 해주어야 한다. 따라서 data['action']['parameters']['OIL_TYPE']['value']
의 값이 "휘발유"라면 "1번", "경유"라면 "2번" 으로 변환해준다.
다음으로 location() 함수로 구한 위치 정보를 a,b변수
에 초기화한 후, trans(a,b) 함수로 WGS84형식의 a,b 값을 TM128형식으로 변환한다.
location() 함수를 이용해서 사용자 위치를 추적 해야 하지만 개인정보 이용 승인을 받지 못했기 때문에 누구스피커에 대하여 해당 함수를 사용할 수 없다. 그래서 아래 코드에서는 임의의 좌표값을 넣어서 실행하고 있다.
browse(a,b,ask_oil_type(ans))함수로 구한 주유소 리스트를 oil_list
에 초기화한 후 action(content()) 함수로 oil_list
의 값을 필요한 형식으로 변환해준다.
make_response(result)함수로 response body형식에 맞추어 정보를 가공한 후 select로 들어온 값 혹은 oiltype으로 들어온 값을 response body에 추가해준다.
request body로 받은 parameter값을 response body에도 입력해주어야 한다.
app = Flask(__name__)
api = Api(app)
class Getparams(Resource):
def post(self):
data = request.get_json()
print(data)
global select
global oil_type
ans = ""
if 'SELECT' in data['action']['parameters'].keys():
select = data['action']['parameters']['SELECT']['value']
if select == "1번" or select == "2번":
ans = select
if 'OIL_TYPE' in data['action']['parameters'].keys():
oil_type = data['action']['parameters']['OIL_TYPE']['value']
if oil_type == "경유":
ans = "2번"
elif oil_type == "휘발유":
ans = "1번"
#a,b = location()
#print(a,b)
a,b = 37.585876,127.143135
a,b = trans(a,b)
global oil_list
oil_list = browse(a,b,ask_oil_type(ans))
print(oil_list)
global data_num
global title
global cost
title = []
cost = []
result = action(content())
response = make_response(result)
if 'SELECT' in data['action']['parameters'].keys():
response["output"]["SELECT"] = select
if 'OIL_TYPE' in data['action']['parameters'].keys():
response["output"]["OIL_TYPE"] = oil_type
print(response)
return jsonify(response)
api.add_resource(Getparams,'/answer.lowprice','/answer.lowprice.diesel','/answer.lowprice.gasoline','/answer.lowprice.diesel.0','/answer.lowprice.diesel.1','/answer.lowprice.gasoline.0','/answer.lowprice.gasoline.1','/answer.lowprice.select.diesel','/answer.lowprice.select.diesel0','/answer.lowprice.select.diesel1','/answer.lowprice.select.gasoline','/answer.lowprice.select.gasoline0','/answer.lowprice.select.gasoline1')
if __name__ == "__main__":
app.run()