Google Map을 활용하여 위치정보 추출하는 Python코드

출퇴근 정보 추출

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

 

googlemap
google map timeline

 

처리 순서

Google Map기반 출퇴근 정보 처리의 데이터 처리 순서는 다음과 같다.

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

      Takeout 파일 구성
      Takeout 파일 구성

 

 

 

 

 

 

 

  • 근무지 Zone 확인 :  Google Map 의 위도  / 경도 정보를 통하여 위치 정보가 기록 되어 있으므로 이동 위치 정보가 근무지 Zone에 들어갔는지 나왔는지 확인 하기 위해 필요하다.
    • 근무지가 지사나 이사를 통해 여러 군데 위치할 수 있다.
  • 출근 일시 : 원하는 시작일자부터 종료 일자까지 근무지 Zone 에 들어간 날자가 바로 출근 증빙 자료가 된다.
  • 휴일 또는 휴가 일정 확인 : 휴일이나 휴가 시에는 근무지 Zone에  들어가지 않는다.

 

구글 정보 Load

아래코드는 Google 위치 정보를 로드하는 코드이다.

g_pos = None
with open('./Records.json', 'r') as fp:
    g_pos = json.load(fp)
Records.json
Records.json

휴일, 휴가, 재택, 증빙

근무지 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파일 로드)를 순차적으로 읽어 처리를 한다.

다음은 메인 처리 순서 및 소스이다.

  1. load json : Google 계정의 Takeout으로 생성한 json 파일을 로드한다.
  2. 근무일 설정 : 출퇴근 정보를 추출할 시작일과 종료일을 설정한다.
  3. 근무일을 순차적으로 loop돌며 데이터를 정리한다(s_date_zw_wk_list(결과물), dict_date_zw_wk(처리를 위한 db))
  4. 구글위치정보를 순차적으로 처리하며 근무Zone에 드나드는 시간을 체크한다.
  5. 결과 출력 : s_date_zw_wk_list(결과물) 정보를 기반으로 출력한다.
    1. 이 부분을 확장하여 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에 오간 통계나 실시간 알림을 보내는 프로그램도
만들 수 있겠다 생각해봤다.

감사합니다.

Leave a Comment