본문 바로가기
연습장/python

[python] 경도/위도 데이터를 활용한 지도 시각화

by Ruas 2023. 7. 5.
728x90

이번 포스트에서는 경도/위도 데이터를 활용하여 지도 위에 위치를 표시하는 지도 시각화에 대해 알아본다.

 

Dataset의 상세 정보는 다음과 같다.

 

✅ 전국주차장정보표준데이터 중 제주특별자치도의 데이터만 사용한다.

✅ 해당 데이터는 각 지자체별로 등록되어 있으므로, 제주시와 서귀포시 데이터를 다운받으면 된다.

✅ 서귀포시 데이터의 경우 경도/위도가 모두 표함되어 있지만, 제주시 데이터는 그렇지 않다.

✅ 두 데이터 모두 지번주소 데이터가 존재한다.

 

 

전국주차장정보표준데이터

국가에서 보유하고 있는 다양한 데이터를『공공데이터의 제공 및 이용 활성화에 관한 법률(제11956호)』에 따라 개방하여 국민들이 보다 쉽고 용이하게 공유•활용할 수 있도록 공공데이터(Datase

www.data.go.kr

 

이번 포스트를 통해 진행할 내용은 다음과 같다.

 

✅ 제주시 데이터의 경도/위도 데이터를 삽입한다. (Kakao API)

✅ 두 데이터 중 불필요한 데이터는 제거한다.

✅ 두 데이터를 지도 위에 표기할 수 있도록 별도 가공한다.

✅ 지도 시각화를 완성한다.

 

 💡 사용 라이브러리
import pandas as pd
import folium
import numpy as np​

1. 데이터 로드

 

jeju_parking = pd.read_csv('제주특별자치도_제주시_주차장정보_20230413.csv', encoding='EUC-KR')
seogwipo_parking = pd.read_csv('제주특별자치도_서귀포시_주차장정보_20221215.csv', encoding='EUC-KR')

2. 데이터 체크 및 불필요 데이터 삭제

 

jeju_parking.columns

 

위의 코드를 통해 현재 dataset의 column들을 확인하면 다음과 같다.

여기서 우리는 불필요한 데이터를 제거하고 다음단계로 넘어간다.

제주시 데이터와 서귀포시 데이터 모두 동일한 column을 가지고 있기 때문에, 아래 과정도 모두 동일하다.

아래 과정은 제주시 데이터를 기준으로 진행하겠다.

 

jeju_parking.drop(['소재지도로명주소', '급지구분', '부제시행구분', '월정기권요금', '평일운영시작시각', '평일운영종료시각', '토요일운영시작시각', '토요일운영종료시각',
                    '공휴일운영시작시각', '공휴일운영종료시각', '요금정보', '주차기본시간', '주차기본요금', '추가단위시간', '추가단위요금', '1일주차권요금적용시간',
                    '특기사항'], axis=1)

정상적으로 column과 데이터들이 제거된 것을 확인할 수 있다.


3. 결측값(NULL) 체크

 

데이터 설명에서는 이미 언급했지만,

기본적으로 데이터를 활용하기 전에는 결측값을 체크하는  것이 좋다.

 

짧은 데이터의 경우에는 육안으로도 충분히 결측값을 확인할 수 있겠지만,

데이터의 양이 많아질 수록 어려워진다.

 

때문에 데이터를 가공, 활용하기 전에 결측값을 먼저 확인한다.

 

먼저 제주시 데이터를 확인해보자.

 

jeju_parking.isna()

 

isna() 함수를 사용하게 되면, 데이터 프레임에서 NULL값(NaN)이 위치한 곳을 TRUE로 반환한다.

 

 

동일한 함수를 사용해서 서귀포시 데이터를 확인하면,

 

경도/위도 데이터에 FALSE가 위치하고 있음을 확인할 수 있다.


4. 결측값 처리 - Kakao API

 

데이터를 활용하기 위해 결측값을 메워주는 작업을 진행한다.

 

제주시 데이터에서 결측값인 경도/위도 데이터는 소재지지번주소 데이터를 활용하여 얻을 수 있다.

방법에 대한 자세한 내용은 아래 포스트에서 확인할 수 있다.

 

 

[Kakao] 카카오 API를 활용한 경도/위도 데이터 수집

오늘 살펴볼 것은 주소 데이터를 활용한 경도/위도 수집 방법이다. 경도/위도 데이터를 얻는 방법은 사실 API를 사용하지 않아도 구글 맵에서 충분히 얻을 수 있다. 위의 사진과 같이 경도/위도

ruas-coding.tistory.com

 

for i in range(len(jeju_parking)):
    print("[",i,"] Searching...", jeju_parking['소재지지번주소'][i])
    locales = lonlat.search(jeju_parking['소재지지번주소'][i])
    for locale in locales:
        jeju_parking['위도'][i] = locale.latitude
        jeju_parking['경도'][i] = locale.longitude

 

info() 함수를 사용해서 제주시 데이터를 확인하면,

 

 

NaN 값이 있던 위도, 경도 columns에 모두 데이터가 입력되어 있음을 확인할 수 있다.


5. 데이터 가공

 

지도에 위치를 찍기 위해서는 경도/위도 데이터만을 가지고 있는 list 타입의 데이터가 필요하다.

이를 위해 데이터 프레임에 있는 데이터를 추출하는 방식으로 가공을 진행한다.

 

jeju_parking.replace('', np.nan, inplace = True)
jeju_loc = jeju_parking[['위도', '경도']].values[:len(jeju_parking)].tolist()
jeju_loc = np.array(jeju_loc, dtype=np.float32).tolist()

 

여기서 주의해야할 점은 리스트 안에 있는 데이터의 타입이 float 타입이어야 한다는 점이다.

int 타입으로 변환이 가능한지는 확인하지 않았으나, 데이터 변조 가능성이 있으며,

char 타입으로 변환이되어 삽입되는 경우에는 지도에 마킹 하는 과정에서 에러가 발생한다.

반드시 마지막에 있는 dtype을 지정해야 한다.

 

 

서귀포시 데이터 역시 동일하게 가공해준다.

 

seogwipo_parking.replace('', np.nan, inplace=True)
seogwipo_loc = seogwipo_parking[['위도', '경도']].values[:len(seogwipo_parking)].tolist()
seogwipo_loc = np.array(seogwipo_loc, dtype=np.float32).tolist()

6. 지도 시각화

 

지도 시각화의 경우 Circle 타입의 원형 마커 타입과 지도 서비스에서 볼 수 있는 Marker 디자인 등과 같이 다양한 디자인을 택할 수 있다.

이번에는 기본 타입인 Circle 타입과 Marker 타입을 적용하는 방식으로 진행한다.

 

Part 1. Circle Type

 

center = [33.3617, 126.5292]
m = folium.Map(location=center, zoom_start=10)

for i in range(len(jeju_loc)):
    jeju_id = jeju_parking['주차장명'][i]
    
    folium.Circle(location=jeju_loc[i], radius=5, color='green', fill=True, fill_opacity=1, tooltip=jeju_id).add_to(m)
    
for j in range(len(seogwipo_loc)):
    seogwipo_id = seogwipo_parking['주차장명'][j]
    
    folium.Circle(location=seogwipo_loc[j], radius=5, color='green', fill=True, fill_opacity=1, tooltip=seogwipo_id).add_to(m)
    
m

 

지도 시각화를 위해서는 제일 먼저 중심점을 지정해야한다.

해당 지도는 세계지도로, 내가 원하는 위치에서 어느 정도의 줌을 필요로하는지 별도로 입력해줘야 한다.

center 변수에 한라산의 경도/위도를 입력하고, zoom_start를 10으로 설정하여 제주도 전체 지도가 한 화면에 표시될 수 있도록 하였다.

 

다음으로 제주시와 서귀포시로 나누어져 있는 데이터를 모두 지도에 표시할 수 있도록 반복문을 사용한다.

이전 단계에서 두 데이터를 combine 시키면 한번만 돌려도 되지만, 그러지 않았으니 

각 데이터별로 한번씩 반복문을 돌리도록 한다.

 

for문 안의 xx_id 변수는 해당 위치의 원을 클릭하였을 때 출력될 내용이다.

Circle 타입의 경우 팝업되는 정보창이 별도로 없어 많은 정보를 포함하기에는 부적절하니,

팝업 정보창이 필요한 경우에는 marker 타입을 사용해야한다.

 

색상, 원의 반경, 채움, 투명도 등의 설정은 folium.Circle에서 가능하니,

필요에 맞게 변형하여 사용하면 된다.

 

 

Part 2. Marker Type

 

center = [33.3617, 126.5292]
m = folium.Map(location=center, zoom_start=10)

for i in range(len(jeju_loc)):
    jeju_id = jeju_parking['주차장명'][i]
    jeju_cat = jeju_parking['주차장구분'][i]
    jeju_addr = jeju_parking['소재지지번주소'][i]
    jeju_cnt = jeju_parking['주차구획수'][i]
    
    iframe = jeju_id + "( " + jeju_cat + " )" + "\n<br>" + jeju_addr + "\n<br>" + "설치면수: " + str(jeju_cnt)
    popup = folium.Popup(iframe, min_width=200, max_width=200)
    folium.Marker(location=jeju_loc[i], popup=popup, tooltip = jeju_id, icon = folium.Icon(color='darkblue')).add_to(m)
    
for j in range(len(seogwipo_loc)):
    seogwipo_id = seogwipo_parking['주차장명'][j]
    seogwipo_cat = seogwipo_parking['주차장구분'][j]
    seogwipo_addr = seogwipo_parking['소재지지번주소'][j]
    seogwipo_cnt = seogwipo_parking['주차구획수'][j]
    
    iframe = seogwipo_id + "( " + seogwipo_cat + " )" + "\n<br>" + seogwipo_addr + "\n<br>" + "설치면수: " + str(seogwipo_cnt)
    popup = folium.Popup(iframe, min_width=200, max_width=200)
    folium.Marker(location=seogwipo_loc[j], popup=popup, tooltip = seogwipo_id, icon = folium.Icon(color='blue')).add_to(m)
m

 

Marker 타입 역시 기본적인 틀은 Circle 타입과 동일하다.

차이점이라면 팝업 정보창에 들어갈 내용들을 지정하는 부분이다.

 

xx_id와 같이 변수에 정보들을 저장하고,

iframe 변수에 string 형식으로 합쳐 folium.Popup() 에 팝업 정보창 내용으로 지정한다.

이 과정에서 팝업창의 사이즈 역시 조절할 수 있다.

 

popup변수에 저장된 팝업 정보창 정보는 folium.marker의 popup 인자에 전달한다.

 

색상, 원의 반경, 채움, 투명도 등의 설정은 folium.Marker에서 가능하니,

필요에 맞게 변형하여 사용하면 된다.

 

 

해당 마커를 클릭하면, 입력한 정보가 뜨는 것을 확인할 수 있다.

 

+ Marker 디자인 변경

 

 

Components · Bootstrap

Extend form controls by adding text or buttons before, after, or on both sides of any text-based . Use .input-group with an .input-group-addon or .input-group-btn to prepend or append elements to a single .form-control. Textual s only Avoid using elements

getbootstrap.com

 

Marker 디자인의 경우 위 사이트에 있는 디자인을 사용할 수 있다.

 

Source: getbootstrap

 

사용하고 싶은 아이콘을 골라 이름의 뒷 단어만 folium에 지정하는 식으로 진행한다.

첫 번째 칸에 위치한 아이콘을 사용하고 싶다면 'asterisk'라는 단어만 입력해주면 된다.

 

folium.Marker(location=seogwipo_loc[j], popup=popup, tooltip = seogwipo_id, icon = folium.Icon(color='blue', icon='th')).add_to(m)

 

center = [33.3617, 126.5292]
m = folium.Map(location=center, zoom_start=10)

for i in range(len(jeju_loc)):
    jeju_id = jeju_parking['주차장명'][i]
    jeju_cat = jeju_parking['주차장구분'][i]
    jeju_addr = jeju_parking['소재지지번주소'][i]
    jeju_cnt = jeju_parking['주차구획수'][i]
    
    iframe = jeju_id + "( " + jeju_cat + " )" + "\n<br>" + jeju_addr + "\n<br>" + "설치면수: " + str(jeju_cnt)
    popup = folium.Popup(iframe, min_width=200, max_width=200)
    folium.Marker(location=jeju_loc[i], popup=popup, tooltip = jeju_id, icon = folium.Icon(color='darkblue', icon='th')).add_to(m)
    
for j in range(len(seogwipo_loc)):
    seogwipo_id = seogwipo_parking['주차장명'][j]
    seogwipo_cat = seogwipo_parking['주차장구분'][j]
    seogwipo_addr = seogwipo_parking['소재지지번주소'][j]
    seogwipo_cnt = seogwipo_parking['주차구획수'][j]
    
    iframe = seogwipo_id + "( " + seogwipo_cat + " )" + "\n<br>" + seogwipo_addr + "\n<br>" + "설치면수: " + str(seogwipo_cnt)
    popup = folium.Popup(iframe, min_width=200, max_width=200)
    folium.Marker(location=seogwipo_loc[j], popup=popup, tooltip = seogwipo_id, icon = folium.Icon(color='blue', icon='th')).add_to(m)
m

 


7. HTML 저장

 

이렇게 만든 지도 시각화 파일은 html 파일로 저장할 수 있다.

 

m.save('marker2.html')

전체 코드와 html을 확인하고 싶으면 아래 깃허브에서 확인하기 바란다.

 

 

GitHub - SCUTUM98/Ruas_Factory

Contribute to SCUTUM98/Ruas_Factory development by creating an account on GitHub.

github.com

 

728x90

'연습장 > python' 카테고리의 다른 글

[python] 두 지점간 직선거리 구하기  (0) 2023.06.27

댓글