Today
-
Yesterday
-
Total
-
  • 대구 지하철 시간표 알리미를 만들어 보았다
    Soliloquy 2022. 9. 25. 23:56

    파이썬 로고

     

    최근 대구 도시철도 열차 도착 시간 알림 프로그램 제작에 푹 빠졌었다. 휴대폰에는 대구 도시철도의 도착 시간을 알려주는 여러 앱이 나와있지만, 데스크톱에는 해당 프로그램을 찾을 수 없었다. 그래서 내가 직접 만들어 보면 어떨까?라는 생각으로 만들어 보기로 했다.

     

    그런데 아무 생각 없이 시작한 게 취미가 되어버렸다. 점심시간이나 집에 오면 프로그래밍을 가장 먼저 생각할 정도였다. 때로는 자는 시간을 줄이면서 까지 버그를 잡기도 하고, 새로운 아이디어가 떠오르면 한 줄만 더 쓰자라고 생각하면서 피곤한 몸으로 컴퓨터 앞에서 코드를 만들어 나간 적도 있었다.

     

    내가 만든 열차 시간 알리미

     

    이렇게 까지 프로그래밍에 관심을 두게 된 것은 파이썬이라는 언어의 친절함과 편리함이 매우 컸다. 파이썬은 문법이 쉽고 간결해서 내가 생각하는 것을 구현하는 게 쉬웠다. 그리고 내가 작성한 코드의 어느 부분에서 문제가 발생했는지 코드 줄 단위로 알려주어서 해결 방법을 찾기도 쉬웠다. 이러한 쉬운 난이도 덕에 프로그래밍에 재미가 붙었다.

     

    프로그래밍의 시작

    프로그램 제작 전, 어떤 프로그램 언어를 사용할지 고려해 보았고, 파이썬을 선택했다. 유명하기도 하고 예전에 잠깐 파이썬을 사용해 봤을 때 다른 언어에 비해 쉽다고 느껴졌기 때문이었다. 파이썬에서는 Tkinter라는 기본 패키지, 보다 화려하고 다양한 기능을 지원하는 PyQT나 wxPython 등의 외부 패키지를 이용하여 GUI 프로그램을 만들 수 있었다.

     

    나는 아래와 같은 장점 때문에 Tkinter를 선택했다. 그리고 아래의 장점을 최대한 살리기 위해 외부 라이브러리는 배제하면서 만들기로 했다.

     

    1. 기본 패키지에 포함되어 있어서 별도의 설치가 필요 없다.
    2. 운영체제 영향을 덜 받는다. 윈도우, 맥, 리눅스에서 모두 사용 가능하다.

     

    시작

    우선 어떤 기능/특징을 정하고 그런 것 들을 어떻게 만들지 생각했다. 기능은 이것저것 구상해봤지만, 파이썬의 기본 패키지 라이브러리만을 이용하기에 기능에 제약이 따랐다. 내가 프로그래밍에 미숙함도 한 몫했다.

     

    파일명: 프로그래밍 Todo.txt

    1. 소스코드는 공개한다 (GPL로...)
    2. 열차의 위치는 시간표를 이용하여 추정한다. 시간표보다 늦는 경우가 있으니 +-1분 정도(변경 가능)의 여유를 두는 기능이 있어야 한다.
        -> 안 그래도 될 것 같다.
    3. 평일/토요일/일요일, 공휴일은 네이버나 다음을 이용해서 받아온다.
    4. 열차의 위치는 현재 시간이 시간표상 앞 차와 뒷 차 사이에 위치한다면 열차가 해당 역을 지난 것으로 판단한다.
        -> 시간표 데이터 상 그럴 필요가 없음
    5. 항상 위 기능이 존재해야 한다. 사용자가 항상 위 기능을 선택할 수 있어야 한다.
        -> 최소화 버튼이 있어서 선택 기능은 필요가 없다
    6. 해상도에 관계없이 우측 하단 작업표시줄에 지정된 크기만큼 표기되어야 한다.
        -> 외부 모듈을 사용해야 한다.
    7. 최소화 시 시스템 트레이에 넣을 수 있어야 한다.
        -> 외부 모듈을 사용해야 하므로 보류

    과거 작성한 기록

    여러 번 수정됐다.

     

    열차 정보 받아오기

    가장 핵심적인 기능은 열차의 역사 도착 시간 정보를 계산하는 일이었다. 그러기 위해서는 열차 시간표나 열차 위치 정보를 알려주는 서비스를 사용할 수 있어야 했다.

     

    공공데이터 포털을 찾아본 결과 대구 도시철도는 실시간 위치 정보를 제공하지 않았다. 정확한 열차 위치 정보를 받을 수 없다는 것은 아쉬웠다. 그래서 대신 열차 시간표를 이용하기로 했다.

     

    데이터를 다루기 쉽게 시간표는 CSV 파일로 제공되었고, 내부에는 도착과 출발 시각, 열차 번호 정보 등이 기록되어 있었다. 이를 이용하면 첫차/막차 시각, 이번 열차가 도착 시각, 이번 열차의 목적지에 관한 정보 등[각주:1] 필요한 정보를 모두 확인할 수 있었다.

     

    공공데이터 포털 화면 (좌), CSV 파일 내용 (우)

     

    파이썬에서 CSV 파일을 읽는 방법은 엄청 간단했다. 파이썬의 기본 패키지인 csv를 이용하면 CSV파일 내용을 간단하게 리스트 형태로 가져올 수 있었다. 이어서 이번 열차를 찾는 방법도 크게 어렵지 않았다.

     

    import csv
    
    with open('timetable.csv', 'r', encoding='euc-kr') as timetable_csv: 
        timetable_list = list(csv.reader(timetable_csv))
        print(timetable_list[1])
    
        
    >> ['평일(상)', '영남대', '출발', '', '', '', '05:30:00', '',...

    2호선 시간표 CSV 파일을 읽은 모습

     

    ##이번 열차를 출력하는 불러오는 모듈
    def timetablesearch(list=timetable_list, week="평일",
                        direction="상", station="용산",
                        adtype="도착", SearchTime=''):
    
        SearchTInt = tstrlist2int(SearchTime.split(':')) #시간을 초로 변경
        
        for line in list:
            if (week      in line[0] and
                direction in line[0] and
                station   in line    and
                adtype    in line):
            
                for colline in line:
                    if(":" in colline):                    
                        timetableinfo = tstrlist2int(colline.split(":"))
                        if SearchTInt < timetableinfo:
                            return colline
                        
                for colline in line: ##이후의 시간 대에 편성 없을 경우 예외 경우로 사용
                    if(":" in colline):
                        return colline
                 
        return -1
        
        
    def tstrlist2int(strlist):
    try:
        intlist = list(map(int,strlist))
        return intlist[0]*3600+intlist[1]*60+intlist[2]
    except ValueError:
        return -1
        
    timetablesearch(SearchTime="21:00:00")
    >>'21:01:05'

    초창기 사용했던 코드

     

    만들 때는 생각보다 어렵지 않네...라며 생각하면서 코드를 써내려 갔다가 아래와 같은 문제점을 찾아냈다. 별 것 아닌 문제처럼 보이지만 이를 해결하기 위해 고민하고 또 고민했다. 이 문제를 해결하기 위한 간결한 코드를 찾을 때까지 GUI를 완성시키면서 틈틈이 수정해 나갔다.

     

    1. 첫 차의 경우 시간표 상에 도착이 아닌 출발만 있어서 이를 고려해야 한다.
    2. 열차가 24시를 기점으로 도착 시간이 "0:00:00"으로 기록되어 있어서 열차 시간 검색 시 제대로 검색이 되지 않는 경우가 있음

     

    GUI 만들기

    CSV 파일 처리 구현은 어느 정도 완성 되었고, 이제 Tkinter 기반의 GUI를 만들 차례였다. GUI 구상은 경험이 없는 탓에 www.studytonight.com/tkinter/에서 Tkinter의 기본 사용 법을 먼저 익히면서 하나씩 만들어갔다. 만들기 전만 해도 어려울 것 같은데?라는 생각이 들었는데 막상 따라가니 크게 어렵지 않았다.

     

    윈도우 창에 띄우는 버튼, 텍스트, 리스트 박스 등의 구성 요소를 위젯이라고 하는데 Tkinter는 이러한 위젯을 배치하기 쉬웠다. tkinter.Tk()라는 창 객체를 만들고, 그 안에 어느 창에다가 배치할지를 정하는 master 옵션을 정해주고 배치 방법(.pack, .grid, .place)을 정해주면 끝이었다.

     

    배치 방법을 .pack으로 하면 정해진 창에 위젯을 추가할 때마다 블록처럼 쌓였다. 텍스트를 표시해주는 Label 위젯을 여러 개 만든 다음 .pack으로 배치하면 .pack을 한 Label 위젯 순서대로 차곡차곡 쌓였다.

     

    .grid의 경우 표에서 행열을 정해서 표시할 수 있었고,  .place의 경우 x, y좌표로 위젯을 나타낼 수 있었다.

     

    import tkinter as tk
    
    win = tk.Tk()
    win.title(f'열차 시간 알리미')
    
    StationSelection = "용산"
    HolidaySelection = "휴일"
    
    
    MFrame = tk.LabelFrame(master = win, labelanchor="n", text=f'{StationSelection} / {HolidaySelection}')
    MFrame.pack(fill=tk.BOTH, expand=True)
    
    Label1 = tk.Label(master = MFrame, text="테스트1")
    Label1.pack()
    
    Label2 = tk.Label(master = MFrame, text="테스트2")
    Label2.pack()

    Tkinter 예시 코드

     

    예시 코드 실행 모습

     

    내가 작성한 코드 두 줄이 GUI로 바로 출력된다는 나오는 것이 엄청 신기했고, 재미있었다. 그래서 메인 화면까지는 단숨에 만들어 나갔다. 만들면서 쓸만한 기능이 생각나면 바로바로 코드로 구현했다. 당연하게도 프로그램이 갑자기 동작이 중단되는 둥 문제가 많았다. 하나씩 버그를 수정하면서 GUI를 조금씩 완성해 나갔다.

     

    형태를 갖춘 메인 화면

     

    인터넷에서 휴일 정보 다운로드

    열차는 평일/토요일/휴일마다 운행 시간표가 다르다. 프로그램 내에서 사용자가 임의로 휴일 여부를 설정하면 되기는 하지만, 자동으로 바뀌면 더 편할 것 같았다. 일자별 휴일 여부 데이터를 미리 만들어 두는 방법도 있긴 있으나 인터넷에서 받아오는 방법을 택하기로 했다. 항상 자동으로 업데이트되어 매번 수정할 필요가 없고 더 신뢰성이 있기 때문이다.

     

    우선 네이버 지도에서 받아올 수 있을까?라는 생각이 들었다. 네이버 지도에서 웹브라우저 개발자 도구를 열고 네트워크 에서 휴일 정보 관련 정보를 어떻게 받아오는지 찾아봤다.

     

    PC버전의 네이버 지도에서는 휴일 정보가 173KB의 .json 파일에 포함되어 있음을 확인했고, 모바일 버전의 네이버 지도에서는 71바이트의 파일에 포함되어 있음을 확인했다. 모바일 버전의 파일을 받아 오는 것이 데이터 처리 속도 면에 있어서 이점일 것이라 생각하여 모바일 버전의 데이터를 이용하기로 했다.

     

    PC버전 네이버 지도에서 받아온 파일 내용

     

    {
      "result": {
        "dateType": 3,
        "timestamp": 1662971610000
      }
    }

    모바일 버전 네이버 지도에서 받아온 파일 내용

    dateType 값에 따라서 평일/토요일/휴일이 결정된다

     

    파이썬에서는 데이터를 받아오고, 처리하는 것도 쉬웠다. 기본 모듈인 urllib.request를 통해 파일을 다운로드하고, json 모듈을 통해 내가 원하는 정보만 가져올 수 있었다.

     

    import time
    import urllib.request
    import urllib.error
    import json
    
    def GetTodayServiceDayNaver():
        try:
            CurrentTimeStr = time.strftime("%Y%m%d%H%M%S")
            NaverMapAPI = f'*={CurrentTimeStr}'
            DataURL = urllib.request.urlopen(NaverMapAPI)
            Data = DataURL.read()
            Encoding = DataURL.info().get_content_charset()
            jsonData = json.loads(Data.decode(Encoding))
            todayServiceDay = jsonData['result']['dateType']
            typeDict = {1: "평일",
                        2: "토요일",
                        3: "휴일"}
                
            return typeDict.get(todayServiceDay, "네이버에서 정확한 값을 받을 수 없습니다.")
            
        except urllib.error.HTTPError as e:
            print(e.__dict__)
            return "모듈로 부터 에러"

    네이버 지도로부터 휴일 정보를 받아오는 코드 일부

     

    하지만, 이 방법은 공식적으로 네이버에서 제공하는 방법이 아니라서 추후 어떻게 링크가 바뀔지 알 수 없었다. 그래서 추후 다른 방안을 찾아야 했으며, 최종적으로는 공공 데이터 포털의 한국천문연구원 특일 정보로부터 받아오기로 했다.

     

    추가 기능 제작

    시간표 파일 최신 버전 자동 다운로드

    어느 정도 사용할 수 있을 만큼 프로그램을 구성한 뒤 여러 가지 추가 기능을 넣어보기도 했다. 공공데이터 포털에서 시간표 파일에 관한 메타데이터를 받을 수 있는데, 메타데이터 내부에는 파일이 언제 수정됐는지 기록되어 있었다.

     

    이를 이용하여 현재 가지고 있는 시간표 파일이 최신 버전인지 확인하고 받아오는 버전 확인 기능도 만들 수 있었다. 다만 이 기능은 도시철도의 경우 시간표가 거의 바뀌지 않아서 괜히 리소스 낭비라고 생각하여 최근 버전에서는 제거됐다.

     

    {
    
        "name":"대구도시철도공사_2호선 열차시각표",
        "description":"대구도시철도 2호선 열차시각표로 열차번호별 평일상선, 평일하선, 토요일상선, 토요일하선, 휴일상선, 휴일하선별로 열차 출발 및 도착시간을 제공하고 있습니다.",
        "url":"https://www.data.go.kr/data/3033376/fileData.do",
        "keywords":[
            "대구도시철도 2호선 열차시간,열차시간,열차시각"
        ],
        "license":"https://data.go.kr/ugs/selectPortalPolicyView.do",
        "dateCreated":"2022-05-16",
        "dateModified":"2022-05-16",
        "datePublished":"2022-05-16",
        "creator":{
            "name":"대구교통공사",
            "contactPoint":{
                "contactType":"정보지원부",
                "telephone":"+82-",
                "@type":"ContactPoint"
            },
            "@type":"Organization"
        },
        "distribution":[
            {
                "encodingFormat":"CSV",
                "contentUrl":"https://www.data.go.kr/cmm/cmm/fileDownload.do?atchFileId=FILE_000000002535606&fileDetailSn=1&insertDataPrcus=N",
                "@type":"DataDownload"
            }
        ],
        "@context":"https://schema.org",
        "@type":"Dataset"
    
    }

    JSON 데이터 내용

     

    이 외에...

    이 외에 여러 가지 기능들을 넣어보았다. 시간 가는 줄 모르고 열심히 만들었다.

    • 열차 시간표 다운로드 기능
    • 해당 방면의 종점까지 운행하지 않는 경우 이를 표시
    • 마지막 역 선택을 기록하고 관리
    • 역사별 시간표, 열차별 시간표 작성
    • 남은 시간 표시 및 제거 기능
    • 호선 변경 기능

     

    당시 추가했던 시간표 창

     

    코드 재작성

    하지만, 다양한 기능들을 구현하면서 몇 가지 문제점이 생겼다.

     

    1. 클래스를 사용하지 않고 코드를 작성하여 코드가 복잡하여 유지보수가 힘들다.
    2. 대기 상태에서 CPU 사용률이 초당 1%으로 높다.

     

    이 문제를 해결하기 위해서... 코드를 다시 설계했다. 이번에는 객체에 관해서 공부한 뒤 객체를 사용한 코드로 다시 작성했다. 다시 작성하면서 버그도 잡아내고, 어떻게 하면 대기 상태에서의 CPU 사용률을 줄일 수 있을까? 프로그램 시작 속도를 어떻게 하면 더 빠르게 할 수 있을까? 여러 가지 개선 방안을 고민하면서 추석 연휴를 보낸 것 같다.

     

    class MainWindow(tk.Tk):
        Title = "열차 시간 알리미"
        
        def __init__(self):
            super().__init__()
            self.Set_Window()
            self.Set_UI()
            self.Set_ProcessClassInit()
            self.Update_DayType()
            self.Update_UI_Label()
            self.Update_TrainInfo()
    
            
        def Set_Window(self):
            self.title (f'{self.Title}')
            self.resizable(False, False)
            self.attributes("-toolwindow", True)
    
        def Set_UI(self):
            self.UIMenuBar = self.UIMenuBarClass(self)
            self.UIFrame = self.UIFrameClass(self)

    클래스를 사용한 프로그래밍

     

     

    이후 버전부터는 대기상태에서는 화면 새로고침에 필요한 정보만 연산하고, 표시했다. 그리고 열차 시간대 검색 방법을 변경한 덕분에 첫차와 막차 정보를 깔끔하게 불러오게 됐다. 그 결과 대기화면 CPU 사용률을 0%에 가깝게 낮췄고, 겸사겸사 기존 코드에 존재하던 각종 버그들도 수정할 수 있었다.

     

    새로 만든 열차시간 알리미

     

    황금 같은 추석 연휴를 코딩으로 시간을 보내어 편히 쉴 수 없었다는 것이 아쉽긴 했지만... 객체지향 프로그래밍에 흥미를 느낄 수 있었어서 유익했다.

     

    남들에게 창작의 즐거움을 느끼고 싶다면 파이썬을 통한 GUI 프로그래밍을 추천한다. 준비 시간도 그렇게 많이 들지 않고, 나름대로의 프로그램도 만들어보고 남들한테 보여주기도 좋은 활동인 것 같다. 내 주위 대부분은 코딩이 취미라고 하면 놀라는 것 같지만...

     

    1. 도시철도의 경우 노선의 끝이 아닌 특정 역까지만 운행하는 경우가 있다. [본문으로]

    댓글

어제는 이곳에 명이 다녀갔습니다.

Powered & Designed by Tistory