YOGYUI

공공데이터포털::전기차 충전소 운영정보 조회 (REST API) 본문

Data Analysis/Data Engineering

공공데이터포털::전기차 충전소 운영정보 조회 (REST API)

요겨 2021. 6. 25. 10:06
반응형

공공데이터포털 Open API 목록을 훑어보던 중, 한국전력공사에서 제공하는 '전기차 충전소 운영정보'가 있어서 한번 구현해봤다

 

내년에 새 집 입주할 때 잔금 다 치르고 자금상황이 좀 안정화되면 전기차 한 대 뽑을까 계획중인데, 충전소 관련된 어플을 직접 만들어볼까 고민중~

 

1. 공공데이터포털 API 활용신청

데이터 타이틀은 "한국전력공사_전기차 충전소 운영정보", URL은 아래 링크 참고

https://www.data.go.kr/iim/api/selectAPIAcountView.do

메타데이터 정보

 

API 활용신청(방법은 링크 참고)하고 인증키 획득

개발계정 상세보기 - 인증키 확인

 

서비스 URL: http://openapi.kepco.co.kr/service/EvInfoServiceV2/getEvSearchList

 

요청 변수는 다음과 같다

특정 주소를 함께 입력할 수 있는 것이 특징이다

 

출력 결과는 다음과 같다

각 충전소별로 충전기 타입, 충전방식, 충전기 상태 등 다양한 정보를 얻어올 수 있다

스마트폰 네비게이션 앱에 충전소 관련 정보를 연동하기에 충분한 정보들을 제공하고 있다

 

2. 테스트 코드

간단하게 Python으로 테스트 코드를 짜보자

import requests
from urllib import parse

url = "http://openapi.kepco.co.kr/service/EvInfoServiceV2/getEvSearchList"
api_key_utf8 = "Your API Key from data.go.kr"
api_key_decode = parse.unquote(api_key_utf8)

params = {
    "ServiceKey": api_key_decode,
    "pageNo": 1,
    "numOfRows": 5
}

response = requests.get(url, params=params)

테스트를 위해 5개만 조회해봤다

In [1]: print(response.text)
Out[1]:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<response>
  <header>
    <resultCode>00</resultCode>
    <resultMsg>NORMAL SERVICE.</resultMsg>
  </header>
  <body>
    <items>
      <item>
        <addr>서울특별시 중구 남대문로 92 1층 주차장</addr>
        <chargeTp>2</chargeTp>
        <cpId>811</cpId>
        <cpNm>급속01</cpNm>
        <cpStat>1</cpStat>
        <cpTp>10</cpTp>
        <csId>14</csId>
        <csNm>서울직할</csNm>
        <lat>37.565199</lat>
        <longi>126.983339</longi>
        <statUpdateDatetime>2021-06-29 23:05:00</statUpdateDatetime>
      </item>
      <item>
        <addr>경상남도 남해군 남해읍 심천리 955-1 정문 우측</addr>
        <chargeTp>2</chargeTp>
        <cpId>464</cpId>
        <cpNm>급속01</cpNm>
        <cpStat>3</cpStat>
        <cpTp>10</cpTp>
        <csId>75</csId>
        <csNm>남해지사</csNm>
        <lat>34.851325596758144</lat>
        <longi>127.8942036494668</longi>
        <statUpdateDatetime>2021-06-29 23:02:07</statUpdateDatetime>
      </item>
      <item>
        <addr>서울특별시 한강대로 23길 55 4.5F층 달주차장</addr>
        <chargeTp>2</chargeTp>
        <cpId>725</cpId>
        <cpNm>급속06</cpNm>
        <cpStat>1</cpStat>
        <cpTp>10</cpTp>
        <csId>120</csId>
        <csNm>용산역 아이파크몰</csNm>
        <lat>37.530561</lat>
        <longi>126.965169</longi>
        <statUpdateDatetime>2021-06-29 23:01:28</statUpdateDatetime>
      </item>
      <item>
        <addr>경상남도 창원시 의창구 도계동 908 쎄씨헤어 앞</addr>
        <chargeTp>1</chargeTp>
        <cpId>229</cpId>
        <cpNm>완속03</cpNm>
        <cpStat>1</cpStat>
        <cpTp>3</cpTp>
        <csId>81</csId>
        <csNm>의창구청</csNm>
        <lat>35.254646</lat>
        <longi>128.639178</longi>
        <statUpdateDatetime>2021-06-29 23:02:59</statUpdateDatetime>
      </item>
      <item>
        <addr>부산광역시 해운대구 우동 1413-4 지하주차장</addr>
        <chargeTp>2</chargeTp>
        <cpId>6891</cpId>
        <cpNm>급속03</cpNm>
        <cpStat>1</cpStat>
        <cpTp>10</cpTp>
        <csId>2605</csId>
        <csNm>벡스코 제2전시장 지하주차장</csNm>
        <lat>35.16556026063979</lat>
        <longi>129.13480091423258</longi>
        <statUpdateDatetime>2021-06-29 23:05:47</statUpdateDatetime>
      </item>
    </items>
    <numOfRows>10</numOfRows>
    <pageNo>1</pageNo>
    <totalCount>2126</totalCount>
  </body>
</response>

조회 가능한 충전소의 총 개수는 (totalCount) 2126개로 나온다

타 정보제공 사이트와는 차이가 큰 것 같긴 한데... 아무래도 민간 충전소는 조회가 되지 않고, 한국전력공사가 직영하는 충전소만 제공하는 것 같다

출처: https://www.greenpeace.org/korea/update/13844/blog-ce-core-contents-evstation/

 

각 충전기의 주소명 및 위도, 경도로 정확한 주소를 알 수 있으며, 조회 시점의 상태(충전중인지, 수리중인지 여부) 및 충전방식(충전기 소켓) 등 다양한 정보를 얻을 수 있다

3. pandas DataFrame

import requests
from urllib import parse
import pandas as pd
from bs4 import BeautifulSoup
from datetime import datetime

url = "http://openapi.kepco.co.kr/service/EvInfoServiceV2/getEvSearchList"
api_key_utf8 = "Your API Key from data.go.kr"
api_key_decode = parse.unquote(api_key_utf8)

params = {
    "ServiceKey": api_key_decode,
    "pageNo": 1,
    "numOfRows": 5000
}

response = requests.get(url, params=params)
xml = BeautifulSoup(response.text, "lxml")
items = xml.find("items")
item_list = []
for item in items:
    item_dict = {
        'addr': item.find("addr").text.strip(),
        'chargetp': int(item.find("chargetp").text),
        'cpid': int(item.find("cpid").text),
        'cpnm': item.find("cpnm").text.strip(),
        'cpstat': int(item.find("cpstat").text),
        'cptp': int(item.find("cptp").text),
        'csid': int(item.find("csid").text),
        'csnm': item.find("csnm").text.strip(),
        'lat': float(item.find("lat").text),
        'longi': float(item.find("longi").text),
        "statupdatedatetime": datetime.strptime(item.find("statupdatedatetime").text.strip(), '%Y-%m-%d %H:%M:%S')
    }
    item_list.append(item_dict)

df = pd.DataFrame(item_list)
In [2]: df.iloc[-1]
Out[2]:
addr                       양정동 981 거제시보건소
chargetp                                2
cpid                                10489
cpnm                                 급속01
cpstat                                  1
cptp                                    1
csid                                 4311
csnm                               거제시보건소
lat                                    33
longi                                 124
statupdatedatetime    2021-06-29 23:26:01
Name: 2125, dtype: object

총 2126개의 아이템이 데이터프레임으로 잘 생성된 것을 알 수 있다

 

API 문서를 보면 요청변수에 'addr'로 특정 주소를 함께 입력하면, 해당 주소의 근방 충전소만 조회해주는 것 처럼 적혀있어서 함수로 만들어봤다

import requests
from urllib import parse
import pandas as pd
from bs4 import BeautifulSoup
from datetime import datetime

def getEvCharger(addr: str) -> pd.DataFrame:
    url = "http://openapi.kepco.co.kr/service/EvInfoServiceV2/getEvSearchList"
    api_key_utf8 = "Your API Key from data.go.kr"
    api_key_decode = parse.unquote(api_key_utf8)
    
    params = {
        "ServiceKey": api_key_decode,
        "pageNo": 1,
        "numOfRows": 5000,
        "addr": addr
    }
    
    response = requests.get(url, params=params)
    xml = BeautifulSoup(response.text, "lxml")
    items = xml.find("items")
    item_list = []
    for item in items:
        item_dict = {
            'addr': item.find("addr").text.strip(),
            'chargetp': int(item.find("chargetp").text),
            'cpid': int(item.find("cpid").text),
            'cpnm': item.find("cpnm").text.strip(),
            'cpstat': int(item.find("cpstat").text),
            'cptp': int(item.find("cptp").text),
            'csid': int(item.find("csid").text),
            'csnm': item.find("csnm").text.strip(),
            'lat': float(item.find("lat").text),
            'longi': float(item.find("longi").text),
            "statupdatedatetime": datetime.strptime(item.find("statupdatedatetime").text.strip(), '%Y-%m-%d %H:%M:%S')
        }
        item_list.append(item_dict)
    
    return pd.DataFrame(item_list)
In [3]: df = getEvCharger("서울")
In [4]: df.shape
Out[4]: (225, 11)

'서울' 키워드를 사용하면 총 225개 아이템이 조회된다

아이템들을 살펴보던 중 특이한 점을 발견했다

In [5]: df.iloc[102]
Out[5]: 
addr                  울산광역시 울주군 삼남면 교동리 1607-2 본관건물 고객지원팀 앞 2층 주차장 안
chargetp                                                           2
cpid                                                             452
cpnm                                                            급속01
cpstat                                                             1
cptp                                                              10
csid                                                              69
csnm                                                       서울산지사(공용)
lat                                                          35.5579
longi                                                        129.113
statupdatedatetime                               2021-06-29 23:38:09
Name: 102, dtype: object

충전소 명칭이 '서울산지사(공용)'인데 여기 '서울' 문자열이 있다고 해서 같이 조회가 된 것 같다 ㅋㅋㅋ

그냥 아이템들 문자열에 키워드가 포함되었는지 여부만 판단해서 리턴하는 것 같다...

 

4. 지도 시각화

충전소 위치를 지도에 그리기 위해 folium 라이브러리를 사용해보자

import folium

def convertGeoLoc(h, m, s): 
    return h + (m + s / 60) / 60
    
df = getEvCharger("서울")

fm = folium.Map(
    location=[convertGeoLoc(37, 33, 6.89), convertGeoLoc(126, 59, 30.664)],
    tiles='cartodbpositron',
    zoom_start=12
)

for _, elem in df.iterrows():
    folium.Circle(
        location=(elem['lat'], elem['longi']),
        tooltip=elem['csnm'],
        radius=100,
        color="#ff0000"
    ).add_to(fm)

fm.save('result.html')

서울시 전기차 충전소 위치

 

5. 내 주변 충전소 찾기

네비게이션 등의 어플리케이션에 활용하기 위해서는 나의 현재 위치를 기반으로 특정 반경 내에 있는 충전소들만 조회하는 기능이 구현되어야 한다

 

지구와 같은 구면 좌표계에서 두 지점의 위도와 경도가 주어졌을 때, 두 지점간의 거리는 Haversine(하버사인) 공식을 활용하면 된다

https://en.wikipedia.org/wiki/Haversine_formula

 

Haversine formula - Wikipedia

The haversine formula determines the great-circle distance between two points on a sphere given their longitudes and latitudes. Important in navigation, it is a special case of a more general formula in spherical trigonometry, the law of haversines, that r

en.wikipedia.org

공식이 복잡하지는 않지만, 직접 구현하기 귀찮으면 파이썬의 haversine 라이브러리를 활용하면 된다 (파이썬의 위대함)

pip install haversine

자세한 사용법은 PyPI 공식 문서 참고

https://pypi.org/project/haversine/

 

haversine

Calculate the distance between 2 points on Earth.

pypi.org

 

다음과 같이 조회되는 충전소들과의 거리도 함께 계산하는 함수를 만들어주자

import requests
from urllib import parse
import pandas as pd
from bs4 import BeautifulSoup
from datetime import datetime
from haversine import haversine

def getEvCharger2(cur_loc_latitude: float, cur_loc_longitude: float) -> pd.DataFrame:
    url = "http://openapi.kepco.co.kr/service/EvInfoServiceV2/getEvSearchList"
    api_key_utf8 = "Your API Key from data.go.kr"
    api_key_decode = parse.unquote(api_key_utf8)
    
    params = {
        "ServiceKey": api_key_decode,
        "pageNo": 1,
        "numOfRows": 5000
    }
    
    response = requests.get(url, params=params)
    xml = BeautifulSoup(response.text, "lxml")
    items = xml.find("items")
    item_list = []
    for item in items:
        lat = float(item.find("lat").text)
        longi = float(item.find("longi").text)
        
        item_dict = {
            'addr': item.find("addr").text.strip(),
            'chargetp': int(item.find("chargetp").text),
            'cpid': int(item.find("cpid").text),
            'cpnm': item.find("cpnm").text.strip(),
            'cpstat': int(item.find("cpstat").text),
            'cptp': int(item.find("cptp").text),
            'csid': int(item.find("csid").text),
            'csnm': item.find("csnm").text.strip(),
            'lat': lat,
            'longi': longi,
            'statupdatedatetime': datetime.strptime(item.find("statupdatedatetime").text.strip(), '%Y-%m-%d %H:%M:%S'),
            'distance': haversine((lat, longi), (cur_loc_latitude, cur_loc_longitude))
        }
        item_list.append(item_dict)
    
    return pd.DataFrame(item_list)

 

테스트삼아, 지금 살고 있는 주소 (광교아이파크)를 중심으로 충전소들의 거리를 구해보자

(좌표 정보는 구글 지도에서 쉽게 구할 수 있다)

df = getEvCharger2(37.274133928822884, 127.06114948517133)
In [6]: df.iloc[0]
Out[6]: 
addr                  서울특별시 중구 남대문로 92 1층 주차장
chargetp                                    2
cpid                                      811
cpnm                                     급속01
cpstat                                      1
cptp                                       10
csid                                       14
csnm                                     서울직할
distance                              33.0864
lat                                   37.5652
longi                                 126.983
statupdatedatetime        2021-06-30 00:15:00
Name: 0, dtype: object

거리(distance)는 33.0864 km로 계산되었다

구글 지도에서 측정한 거리는 33.24 km로, 유사한 값인 것을 알 수 있다

 

다음과 같이 반경 10km 내에 있는 충전소를 필터링할 수 있다

df_filter = df[df['distance'] <= 10.0]
In [7]: df_filter = df[df['distance'] <= 10.0]
Out[7]: (46, 12)

총 46군데가 필터링되었다

 

해당 지점들을 마찬가지로 지도에 그려보자

fm = folium.Map(
    location=[37.274133928822884, 127.06114948517133],
    tiles='cartodbpositron',
    zoom_start=15
)

for _, elem in df_filter.iterrows():
    folium.Circle(
        location=(elem['lat'], elem['longi']),
        tooltip=elem['csnm'],
        radius=100,
        color="#ff0000"
    ).add_to(fm)

fm.save('result.html')

광교아이파크 근방 전기차 충전소 위치

동탄이랑 성남시에 있는 충전소까지 같이 조회가 된다 ㅎㅎ

 

여기에 각 충전소에 지금 충전기의 상태 및 충전기 종류 등을 같이 표시할 수 있으면 상당히 유용한 서비스를 제공할 수 있다

(이미 스마트폰 어플 시장에는 포화되어 있는 서비스인 것 같긴 하지만...^^;;)

 

전기차를 사는 그날까지! 

열심히 돈을 모아야겠다 (기승전Money...)

목표는 포르쉐 마칸!

 

끝~!

반응형