출퇴근 정보 추출
Google Map을 활용하여 위치정보 추출하는 Python코드를 만드려 한다.
Google Map 타임 라인 정보는 안드로이드 또는 아이폰이나 상관 없이 자신의 위치 정보를 실시간으로 남길 수 있다.
위치정보를 기록하기 위한 설정으로 Google Map의 설정에서 위치정보를 항상으로 설정하여야 한다.

처리 순서
Google Map기반 출퇴근 정보 처리의 데이터 처리 순서는 다음과 같다.
- Google Map 정보 다운로드 : Google Takeout을 통한 위치 정보 download
- Google 계정 > 데이터 및 개인 정보 보호 > 데이터 다운로드 또는 삭제 > 데이터 다운로드 > 위치 기록(체크) , 다음 단계 진
- 한 번만 내보내기, 내보내기 생성
- takeout-YYYYMMDDTHHmmSSZ-001.zip 형식의 파일 다운로드

Google 계정 > 데이터 및 개인 정보 보호 
데이터 다운로드 또는 삭제 > 데이터 다운로드 
위치 기록 체크 
Takeout 파일 구성
- 근무지 Zone 확인 : Google Map 의 위도 / 경도 정보를 통하여 위치 정보가 기록 되어 있으므로 이동 위치 정보가 근무지 Zone에 들어갔는지 나왔는지 확인 하기 위해 필요하다.
- 근무지가 지사나 이사를 통해 여러 군데 위치할 수 있다.
- 출근 일시 : 원하는 시작일자부터 종료 일자까지 근무지 Zone 에 들어간 날자가 바로 출근 증빙 자료가 된다.
- 휴일 또는 휴가 일정 확인 : 휴일이나 휴가 시에는 근무지 Zone에 들어가지 않는다.
구글 정보 Load
아래코드는 Google 위치 정보를 로드하는 코드이다.
g_pos = None
with open('./Records.json', 'r') as fp:
g_pos = json.load(fp)

휴일, 휴가, 재택, 증빙
근무지 Zone에 있지 않지만 출근으로 확인해야 하는 다른 상황에 대해 처리하는 로직은 다음과 같다.
- update_date_vacances :
date_vacances={
'2017-10-02~2017-10-06':'^추석',
'2018-02-15~2018-02-16': '^설',
'2018-03-01':'^삼일절',
'2018-05-22':'^부처님오신날',
'2018-09-24~2018-09-26': '^추석',
'2018-02-27~2017-03-06':'출장(계약)',
'2018-05-15' :'출근증빙사진(https://photos.google.com/photo/)',
'2018-05-16' :'출근증빙사진(https://photos.google.com/photo/)',
'2018-06-20~2018-06-24':'출장,---',
'2018-07-13~2018-07-18':'휴가',
'2018-09-24~2018-09-26':'^추석',
'2018-10-30~2018-11-05':'출장,---',
'2019-02-04~2019-02-06': '^설',
'2019-09-12~2019-09-13':'^추석',
'2019-12-25' : '^크리스마스',
'2020-03-12':'출장,대전',
'2020-03-16':'휴가',
'2020-03-18':'출근증빙사진(https://photos.google.com/photo/)',
'2020-03-19':'출근증빙사진(https://photos.google.com/photo/)',
'2020-03-20':'휴가',
'2020-03-24':'출근증빙사진(https://photos.google.com/photo/)',
'2020-04-06':'출근증빙사진(https://photos.google.com/photo/)',
'2020-04-07':'출근증빙사진(https://photos.google.com/photo/)',
'2020-04-09':'출근증빙사진(https://photos.google.com/photo/)',
'2020-06-05':'출근증빙사진(https://photos.google.com/photo/)',
'2020-08-18~2020-08-21':'재택',
'2020-08-24~2022-02-11':'재택',
'2022-04-11~2022-04-18':'재택',
'2022-07-18~2022-07-29':'휴가',
}
def update_date_vacances():
for key_vac in date_vacances.keys():
vac_value = date_vacances[key_vac]
bHoly= True if vac_value.find('^')>=0 else False
if bHoly == True:
vac_value = vac_value[1:]
if key_vac.find('~') >= 0:
vac_dates = key_vac.split('~')
date_start=datetime.datetime.strptime(vac_dates[0], '%Y-%m-%d')
date_end=datetime.datetime.strptime(vac_dates[1], '%Y-%m-%d')
temp_date = date_start
while temp_date <= date_end:
i_wday = temp_date.weekday()
s_temp_date = temp_date.strftime('%Y-%m-%d')
if i_wday < 5:
update_date(s_temp_date, 'comment', vac_value)
if bHoly == True:
update_date(s_temp_date, 'is_wday', False)
temp_date = temp_date + datetime.timedelta(days=1)
else:
update_date(key_vac, 'comment', vac_value)
if bHoly == True:
update_date(key_vac, 'is_wday', False)
일자 처리
아래 코드는 주중 인지 주말인지 예외(휴가등) 일자를 구분하는 함수와 결과를 프린트 함수이다.
- set_date : dict_date_zw_wk 에 중복 되지 않으면 요일정보 기록한다.
- update_date: dict_date_zw_wk에 일자 정보를 업데이트 한다.
- print_date : 결과 출력하는 함수.
s_date_zw_wk_list =[]
dict_date_zw_wk ={}
def set_date(key_date, i_wday):
global s_date_zw_wk_list, dict_date_zw_wk
if key_date not in dict_date_zw_wk.keys():
s_date_zw_wk_list.append({'date': key_date})
bool_iswday = True if i_wday<5 else False
dict_date_zw_wk[key_date]={'is_wday':bool_iswday}
def update_date(key_date, key_data, data_data):
global s_date_zw_wk_list, dict_date_zw_wk
if key_date in dict_date_zw_wk.keys():
dict_date_zw_wk[key_date][key_data]=data_data
def print_date():
idx_gtime_wday_base =1
idx_gtime_wday_t =1
for it_date in s_date_zw_wk_list:
temp_date =it_date['date']
b_wday = dict_date_zw_wk[temp_date]['is_wday']
list_keys =dict_date_zw_wk[temp_date].keys()
# 데이터기반 총 근무일 일수 G타임기록이 있고, 근무일이면 카운트 (idx_gtime_wday_base)
if 'n_day_gmap' in list_keys and b_wday == True:
idx_gtime_wday_base +=1
if 'gowork' in list_keys or 'comment' in list_keys:
idx_gtime_wday_t +=1
print(f'* 구글 타임라인 정보기반의 총 업무일(명절제외) :{idx_gtime_wday_base:,}일 중 {idx_gtime_wday_t}일 증빙일, 출근(증빙)일율: {(idx_gtime_wday_t*100/idx_gtime_wday_base):3.2f}% ')
print()
idx = 1
for it_date in s_date_zw_wk_list:
temp_date =it_date['date']
b_wday = dict_date_zw_wk[temp_date]['is_wday']
list_keys =dict_date_zw_wk[temp_date].keys()
b_target = 'O' if 'n_day_gmap' in list_keys and b_wday == True else 'X'
b_gowork = 'O' if ('gowork' in list_keys or 'comment' in list_keys) and b_wday == True else 'X'
n_timeline = f"{dict_date_zw_wk[temp_date]['n_day_gmap']}" if 'n_day_gmap' in list_keys else ''
sd_datetime_gowork = f"{dict_date_zw_wk[temp_date]['datetime_gowork']}" if 'datetime_gowork' in list_keys else ''
s_comment = f"{dict_date_zw_wk[temp_date]['comment']}" if 'comment' in list_keys else ''
print(f'{idx} {temp_date} {dict_date_zw_wk[temp_date]}')
idx +=1
with open('./google_timeline_gowork.csv', 'a', encoding='utf-8-sig') as fcsv:
fcsv.write(f'{temp_date}, {b_target}, {b_gowork}, {sd_datetime_gowork}, {n_timeline}, {s_comment}\n')
구분 처리
아래 코드는 Google Map을 활용하여 위치정보 추출하는 Python코드의 메인 처리 코드이다.
처리순서는 근무시작일, 근무종료일을 설정하고 s_date_zw_wk_list에 구분하여 저장하여 테이블을 준비한다.
Google Map의 위치 정보(json파일 로드)를 순차적으로 읽어 처리를 한다.
다음은 메인 처리 순서 및 소스이다.
- load json : Google 계정의 Takeout으로 생성한 json 파일을 로드한다.
- 근무일 설정 : 출퇴근 정보를 추출할 시작일과 종료일을 설정한다.
- 근무일을 순차적으로 loop돌며 데이터를 정리한다(s_date_zw_wk_list(결과물), dict_date_zw_wk(처리를 위한 db))
- 구글위치정보를 순차적으로 처리하며 근무Zone에 드나드는 시간을 체크한다.
- 결과 출력 : s_date_zw_wk_list(결과물) 정보를 기반으로 출력한다.
- 이 부분을 확장하여 csv나 xls파일로 변환 시킬 수 있다.
import json, datetime
# 1. load json
g_pos = None
with open('./Records.json', 'r') as fp:
g_pos = json.load(fp)
tot_n_rec = 1
wk_n_rec = 1
nwk_n_rec = 1
# 근무한 총 워크 데이 계산
tot_n_work = 1
wd_n_work = 1
nwd_n_work = 1
s_date_zw_start = '2017-09-01'
s_date_zw_end = '2022-07-31'
date_zw_start=datetime.datetime.strptime(s_date_zw_start, '%Y-%m-%d')
date_zw_end=datetime.datetime.strptime(s_date_zw_end, '%Y-%m-%d')
temp_date = date_zw_start
while temp_date <= date_zw_end:
i_wday = temp_date.weekday()
if i_wday < 5: #주중
wd_n_work += 1
else:
#주말
nwd_n_work += 1
s_temp_date = temp_date.strftime('%Y-%m-%d')
set_date(s_temp_date, i_wday)
# print(temp_date, date_zw_end)
temp_date = temp_date + datetime.timedelta(days=1)
tot_n_work += 1
print(f'* 총 XXXX 근무일: {tot_n_work:,} ({s_date_zw_start} ~ {s_date_zw_end}), 주중:{wd_n_work:,}, 주말:{nwd_n_work:,}')
# 휴가정보 로드
update_date_vacances()
# google json 데이터 파일 로드 및 처리
sDatetime_Start = None
sDatetime_End = None
s_date_last_proc = None
tot_n_day_gmap = 1
tot_n_wd_day_gmap = 1
tot_n_nwd_day_gmap = 1
n_day_gmap = 1
bool_gowork = False
datetime_gowork_start = None
# 근무지1 영역
y1_s = 372987220
x1_s = 1270418450
y2_s = 372964600
x2_s = 1270462760
#근무지2 영역
y1_g = 374821150
x1_g = 1268715370
y2_g = 374766480
x2_g = 1268830200
for it_gpos in g_pos['locations']:
s_datetime=it_gpos['timestamp']
temp_datetime= datetime.datetime.strptime(s_datetime, '%Y-%m-%dT%H:%M:%S.%fZ') if len(s_datetime) == 24 else datetime.datetime.strptime(s_datetime, '%Y-%m-%dT%H:%M:%SZ')
# temp_datetime=s_datetime.strftime('%Y-%m-%dT%H:%M:%S_')
# print(temp_datetime)
#UTC 시간이므로 +9시간 로컬시간으로
temp_datetime = temp_datetime + datetime.timedelta(hours=9)
# print(temp_datetime)
s_datetime = temp_datetime.strftime('%Y-%m-%dT%H:%M:%S')
if sDatetime_Start is None:
sDatetime_Start=s_datetime
sDatetime_End=s_datetime
#요일별 구하기
list_datetime=s_datetime.split('T')
stemp_datetime = f'{list_datetime[0]} {list_datetime[1]}'
# temp_datetime = datetime.datetime.strptime(stemp_datetime, '%Y-%m-%d %H:%M:%S')
# #UTC 시간이므로 +9시간 로컬시간으로
# temp_datetime = temp_datetime + datetime.timedelta(hours=9)
i_wday = temp_datetime.weekday()
if i_wday <5:
wk_n_rec += 1
temp_lat=it_gpos['latitudeE7']
temp_long=it_gpos['longitudeE7']
temp_area = 's' if list_datetime[0] <= '2017-12-18' else 'g' # 근무지2(g)으로 이사 이전은 근무지1(s) 구
if ( temp_area == 's' and
((y1_s > temp_lat and x1_s < temp_long) and (y2_s < temp_lat and x2_s > temp_long))) \
or \
( temp_area == 'g' and
((y1_g > temp_lat and x1_g < temp_long) and (y2_g < temp_lat and x2_g > temp_long))):
bool_gowork = True
if datetime_gowork_start is None:
datetime_gowork_start = stemp_datetime
update_date(list_datetime[0], 'datetime_gowork', datetime_gowork_start)
update_date(s_date_last_proc, 'gowork', True)
else:
# 주말처리
nwk_n_rec += 1
if (s_date_last_proc is None) or (s_date_last_proc != list_datetime[0]):
s_date_last_proc = list_datetime[0]
tot_n_day_gmap += 1
if (i_wday < 5):
tot_n_wd_day_gmap += 1
else:
tot_n_nwd_day_gmap += 1
update_date(s_date_last_proc, 'n_day_gmap', n_day_gmap)
# if bool_gowork == True:
# update_date(s_date_last_proc, 'gowork', bool_gowork)
n_day_gmap = 1
bool_gowork = False
datetime_gowork_start = None
# print(f'{idx} : {s_datetime}')
tot_n_rec+=1
n_day_gmap +=1
print(f'* Google Map record ({sDatetime_Start[:19]} ~ {sDatetime_End[:19]})')
print(f'\ttotal: {tot_n_rec:,} work day: {wk_n_rec:,}, weekend: {nwk_n_rec:,}')
#XXXX 근무일의 업무가능일 대비, 타임라인에 있는 일수
p_gmap_rec = tot_n_wd_day_gmap*100/wd_n_work
print(f'\ttotal day(구글타임라인) : {tot_n_day_gmap:,}, 주중: {tot_n_wd_day_gmap:,}({p_gmap_rec:2.2f}%), 주말: {tot_n_nwd_day_gmap:,}')
print_date()
실행 결과는 다음과 같다.
* 총 XXXX 근무일: 1,795 (2017-09-01 ~ 2022-07-31), 주중:1,282, 주말:513
* Google Map record (2017-09-01T09:04:03 ~ 2022-08-01T08:33:19)
total: 1,088,013 work day: 792,966, weekend: 295,048
total day(구글타임라인) : 1,423, 주중: 1,013, 주말: 411
* 구글 타임라인 정보기반의 총 업무일(명절제외) :995일 중 903일 증빙일, 출근(증빙)일율: 90.75%
1 2017-09-01 {'is_wday': True, 'datetime_gowork': '2017-09-01 09:05:08', 'n_day_gmap': 1, 'gowork': True}
2 2017-09-02 {'is_wday': False, 'n_day_gmap': 515}
3 2017-09-03 {'is_wday': False, 'n_day_gmap': 506}
4 2017-09-04 {'is_wday': True, 'n_day_gmap': 411, 'datetime_gowork': '2017-09-04 08:53:56', 'gowork': True}
5 2017-09-05 {'is_wday': True, 'n_day_gmap': 660, 'datetime_gowork': '2017-09-05 08:53:31', 'gowork': True}
6 2017-09-06 {'is_wday': True, 'n_day_gmap': 1671, 'datetime_gowork': '2017-09-06 08:48:05', 'gowork': True}
7 2017-09-07 {'is_wday': True, 'n_day_gmap': 2032, 'datetime_gowork': '2017-09-07 08:48:29', 'gowork': True}
8 2017-09-08 {'is_wday': True, 'n_day_gmap': 566, 'datetime_gowork': '2017-09-08 08:40:14', 'gowork': True}
9 2017-09-09 {'is_wday': False, 'n_day_gmap': 706}
10 2017-09-10 {'is_wday': False, 'n_day_gmap': 998}
11 2017-09-11 {'is_wday': True, 'n_day_gmap': 1123, 'datetime_gowork': '2017-09-11 08:53:52', 'gowork': True}
12 2017-09-12 {'is_wday': True, 'n_day_gmap': 1386, 'datetime_gowork': '2017-09-12 07:47:29', 'gowork': True}
13 2017-09-13 {'is_wday': True, 'n_day_gmap': 1208, 'datetime_gowork': '2017-09-13 10:50:25', 'gowork': True}
14 2017-09-14 {'is_wday': True, 'n_day_gmap': 1345, 'datetime_gowork': '2017-09-14 09:03:54', 'gowork': True}
15 2017-09-15 {'is_wday': True, 'n_day_gmap': 1251, 'datetime_gowork': '2017-09-15 08:59:05', 'gowork': True}
16 2017-09-16 {'is_wday': False, 'n_day_gmap': 1259}
17 2017-09-17 {'is_wday': False, 'n_day_gmap': 1905}
18 2017-09-18 {'is_wday': True, 'n_day_gmap': 1277, 'datetime_gowork': '2017-09-18 09:08:34', 'gowork': True}
19 2017-09-19 {'is_wday': True, 'n_day_gmap': 1542, 'datetime_gowork': '2017-09-19 08:59:20', 'gowork': True}
20 2017-09-20 {'is_wday': True, 'n_day_gmap': 1154, 'datetime_gowork': '2017-09-20 08:42:33', 'gowork': True}
21 2017-09-21 {'is_wday': True, 'n_day_gmap': 1388, 'datetime_gowork': '2017-09-21 08:08:41', 'gowork': True}
22 2017-09-22 {'is_wday': True, 'n_day_gmap': 1492, 'datetime_gowork': '2017-09-22 08:47:28', 'gowork': True}
.
.
249 2018-05-07 {'is_wday': True, 'n_day_gmap': 717}
250 2018-05-08 {'is_wday': True, 'n_day_gmap': 897, 'datetime_gowork': '2018-05-08 09:54:26', 'gowork': True}
251 2018-05-09 {'is_wday': True, 'n_day_gmap': 803, 'datetime_gowork': '2018-05-09 09:41:35', 'gowork': True}
252 2018-05-10 {'is_wday': True, 'n_day_gmap': 999, 'datetime_gowork': '2018-05-10 09:19:47', 'gowork': True}
253 2018-05-11 {'is_wday': True, 'n_day_gmap': 843, 'gowork': True}
254 2018-05-12 {'is_wday': False}
255 2018-05-13 {'is_wday': False}
256 2018-05-14 {'is_wday': True}
257 2018-05-15 {'is_wday': True, 'comment': '출근증빙사진(https://photos.google.com/photo/AF1QipMwd_mKMGz-5V72gWyKtBXo)'}
258 2018-05-16 {'is_wday': True, 'comment': '출근증빙사진(https://photos.google.com/photo/AF1QipPbcsuE3NZIKcCSzFV5IhDR)'}
259 2018-05-17 {'is_wday': True}
260 2018-05-18 {'is_wday': True}
261 2018-05-19 {'is_wday': False}
262 2018-05-20 {'is_wday': False}
263 2018-05-21 {'is_wday': True}
264 2018-05-22 {'is_wday': False, 'comment': '부처님오신날'}
265 2018-05-23 {'is_wday': True}
266 2018-05-24 {'is_wday': True}
267 2018-05-25 {'is_wday': True}
268 2018-05-26 {'is_wday': False}
269 2018-05-27 {'is_wday': False}
.
.
775 2022-07-11 {'is_wday': True, 'n_day_gmap': 149, 'datetime_gowork': '2022-07-11 13:22:38', 'gowork': True}
1776 2022-07-12 {'is_wday': True, 'n_day_gmap': 316, 'datetime_gowork': '2022-07-12 07:26:03', 'gowork': True}
1777 2022-07-13 {'is_wday': True, 'n_day_gmap': 347}
1778 2022-07-14 {'is_wday': True, 'n_day_gmap': 649, 'datetime_gowork': '2022-07-14 09:10:18', 'gowork': True}
1779 2022-07-15 {'is_wday': True, 'n_day_gmap': 230, 'datetime_gowork': '2022-07-15 08:48:26', 'gowork': True}
1780 2022-07-16 {'is_wday': False, 'n_day_gmap': 319}
1781 2022-07-17 {'is_wday': False, 'n_day_gmap': 239}
1782 2022-07-18 {'is_wday': True, 'comment': '휴가', 'n_day_gmap': 252}
1783 2022-07-19 {'is_wday': True, 'comment': '휴가', 'n_day_gmap': 438}
1784 2022-07-20 {'is_wday': True, 'comment': '휴가', 'n_day_gmap': 295}
1785 2022-07-21 {'is_wday': True, 'comment': '휴가', 'n_day_gmap': 368}
1786 2022-07-22 {'is_wday': True, 'comment': '휴가', 'n_day_gmap': 353}
1787 2022-07-23 {'is_wday': False, 'n_day_gmap': 635}
1788 2022-07-24 {'is_wday': False, 'n_day_gmap': 199}
1789 2022-07-25 {'is_wday': True, 'comment': '휴가', 'n_day_gmap': 197}
1790 2022-07-26 {'is_wday': True, 'comment': '휴가', 'n_day_gmap': 345}
1791 2022-07-27 {'is_wday': True, 'comment': '휴가', 'n_day_gmap': 331}
1792 2022-07-28 {'is_wday': True, 'comment': '휴가', 'n_day_gmap': 461}
1793 2022-07-29 {'is_wday': True, 'comment': '휴가', 'n_day_gmap': 317}
1794 2022-07-30 {'is_wday': False, 'n_day_gmap': 342}
1795 2022-07-31 {'is_wday': False, 'n_day_gmap': 243}
코드의 활용
Google Map을 활용하여 위치정보 추출하는 Python코드를 만들어 보았다.
Google Map 타임라인 정보를 황용하면 특정 Zone에 오간 통계나 실시간 알림을 보내는 프로그램도
만들 수 있겠다 생각해봤다.
감사합니다.
어머넷 블로그에서 더 알아보기
구독을 신청하면 최신 게시물을 이메일로 받아볼 수 있습니다.
댓글은 닫혔습니다.