Home [Spring] 버스 도착정보 수집
Post
Cancel

[Spring] 버스 도착정보 수집

버스 도착 예측 시간 제공

arrival-info

저희 프로젝트의 대학교 앱에서는 버스 도착정보도 제공합니다. 타사의 지도 앱이나 대중교통 정보 앱을 사용하면 더 쉽게 볼 수 있을텐데 왜 대학교 앱에서도 제공할까요?

대학교 정문에서 탈 수 있는 버스는 24번, 8100번, 1100번 등이 있습니다. 하지만 이 버스들은 기점이 정문과 2정거장밖에 차이가 나지 않습니다. 그래서 도착 정보를 조회하려해도 버스가 기점에서 출발하지 않는 한 버스 도착정보를 볼 수 없습니다.

좌석버스

arrival-info2

그래서 버스가 기점에서 출발하고 정문에 도착하기 직전까지만 버스 도착 정보를 볼 수 있습니다. 좌석버스는 배차간격이 20분 이상입니다. 기점에서 정문까지 약 3분 걸린다고 가정하면, 17분정도는 버스 도착정보를 볼 수 없습니다.

이 문제를 해결하기 위해서 버스가 기점에서 출발하는 시간까지 고려해 예상 도착 시간을 제공하기로 했습니다.

24번 버스

좌석버스와 720-3은 배차 간격이 길어서 의미가 있지만, 24번은 배차간격이 5~7분 남짓입니다. 그런데 24번 버스도 도착 시간을 예측해서 제공하고 있습니다.

arrival-info3

그 이유는 24번 버스가 학교로 들어오고, 곰상 정류장에서 10분정도 대기했다가 내려가기 때문입니다. 그래서 24번도 도착 예상 시간을 제공하는 것이 의미가 있습니다. 대중교통 정보 앱에서는 저 지점에서 10분정도 남았다고 예상합니다. 하지만 실제로는 5분만에 출발하는 경우도 있었고, 15분뒤에 출발하는 경우도 있었습니다. 오차가 은근 심한데, 이 부분은 버스 도착정보를 수집하고 데이터를 분석해보기로 했습니다.

셔틀 버스

셔틀 버스는 정해진 시간표가 있습니다. 시간표에 맞춰 도착 예상 시간을 제공하도록 했습니다.

버스 도착정보 수집 방법

경기 버스 정보 API를 활용했습니다. data.go.kr에서 API활용 신청하면 바로 사용할 수 있습니다. 저희는 수집하는 단계에선 버스 위치정보 API만 사용했습니다.

저희가 필요한 정보는 특정 버스특정 정류장에 도착한 시각입니다. 이 시각을 분석해서 버스 도착 시각을 예측할 수 있을거라 생각했습니다.

collect-bus-arrival

그래서 버스의 위치를 주기적으로 조회하면서 특정 정류장을 지나칠 때를 기록합니다. 특정 정거장을 지났는지 어떻게 알 수 있을까요?

collect-bus-arrival2

버스를 실시간으로 추적하면서, 버스의 위치가 특정 정류장의 범위 안에 들어오면 그 버스가 특정 정류장을 지났다고 판단합니다. 하지만 주기적으로 버스 위치를 가져오므로, 버스 위치 정보가 애초에 불연속적인 정보입니다. 그래서 위와 같이 범위를 1개 정류소만큼만 지정하면 버스가 빠르게 지나갔을 때 수집되지 않습니다.

실제로 저는 초기에 버스 도착정보를 수집했을 때 이 방법으로 했다가 빠르게 지나가는 버스를 놓치는 문제가 있었습니다.

collect-bus-arrival3

그래서 이렇게 범위를 넓게 지정해야 빠르게 지나가더라도 수집할 수 있습니다. 한번 수집된 버스는 더 이상 추적하지 않습니다.

이 서버는 NHN Cloud에 배포되어 24시간 계속 구동했습니다. 편리한 배포를 위해 CD환경을 구축했고, docker를 사용했습니다.

이렇게 버스 정보를 주기적으로 수집하고 DB에 저장합니다.

1
2
3
4
5
6
7
8
@Scheduled(fixedDelayString = "${bus-api.frequency}")
public void scheduleBusArrivalRetrieving() {
try {
    service.saveBusArrivalAt();
    log.info("Saved arrival info: " + Instant.now());
} catch (Throwable t) {
    log.error("Error when save bus arrival info", t);
}

당시 데이터베이스는 Mysql을 사용했습니다. 하지만 지금 생각해보면 RDB보다는 NoSQL이 더 적합했을 것 같네요.

  • 수집되는 데이터의 양이 방대하다. (추적하는 버스가 많아서 대략 1분에 10개씩 쌓임)
  • 분석을 위해 수집되는 정보여서 스키마 변경이 잦았다.
  • Table간의 Relation이 필요하지 않다. (join과 같은 테이블단위 연산이 필요없었음)

향후 또 수집할 일이 생기면 NoSQL을 사용해보려고 합니다.

버스 도착 데이터 분석

버스 도착정보를 수집하고, 데이터베이스에 저장했습니다. 이제 이 데이터를 분석해서 Excel에 정리해보았습니다.

arrival-result

3일간 데이터를 모으고 시간차를 비교해보았습니다. 놀랍게도 출발하는 시간대가 거의 비슷합니다. 버스가 기점에서 출발하는 시간표가 있을 것이라 생각했지만 의외로 정확해서 놀랐습니다.

여기서 포인트는 버스 도착 시각을 수집할 때 기점과 가까울 수록 정확하다는 것입니다. 왜냐하면 기점에서 출발하는 시간표가 정해져 있기 때문입니다. 기점과 멀어질 수록 정확도가 떨어지는데, 도로 교통 상황이나 손님 탑승 시간 등 여러 변수가 있기 때문입니다.

그래서 최대한 기점과 가까운 정류장에서 데이터를 수집하고, 정문까지 가는 2-3분을 고려해 합산하여 도착 시간을 예측하기로 했습니다. 결국 버스는 기점에서 출발하는 시간표가 있고, 정문까지 가는 시간이 어느정도 정해져 있으므로, 꽤나 정확하게 정문 도착 시간을 예측할 수 있었습니다.

주의할 점은 공휴일과 평일, 토요일에 따라 시간표가 다르다는 점입니다. 그래서 공휴일과 평일, 토요일에 따라 분리해서 시간표를 뽑아야 합니다.

모든 버스의 도착 시각을 이렇게 순조롭게 예측할 수 있었다면 좋아겠지만 그렇지 않았습니다…

배차 간격이 짧은 버스

arrival-result2

24번 버스는 배차 간격이 5~7분입니다. 이렇게 배차 간격이 짧은 버스는 정확한 예측이 어렵습니다.

  • 기사님이 5~7분단위로 정확한 시간대에 출발하지 않을 수 있기 때문입니다. (배차 간격이 긴 경우에는 시간표가 어느정도 잘 지켜집니다.)
  • 배차 간격이 짧을 수록 버스가 많아지고, 그만큼 수집 데이터의 오차도 커집니다. (즉 데이터가 불균일합니다.)

심지어 24번 버스는 기점에서 출발하는 버스를 예측하는게 아닙니다. 그래서 변수가 훨씬 많고 예측이 어려웠습니다. 그래도 최대한 비슷하게 예측해보려고 몇 가지 시도를 해보았습니다.

시간표 뽑아내기

위의 테이블을 살펴보면 오차로 인해 수집 못한 버스가 있으면 시간이 밀린다는 것을 확인할 수 있습니다. 이걸 우선 맞춰주기 위해서 시간대별로 그룹핑했습니다.

시간대별로 그룹핑

아래와 같은 알고리즘을 구상했습니다.

  1. 월요일을 기준으로 시각 하나씩 선택하며 그룹을 만듭니다.
  2. 화요일, 수요일, … 을 돌며 월요일과 시간 차이가 3분 미만인 시각끼리 그룹핑합니다.
  3. 그룹핑된 시각끼리 묶어 재정렬합니다.

시간은 정렬되어있으므로 binary-search를 사용할 수 있습니다. 그래서 최종 시간 복잡도는 O(nlogn)입니다. 사실 시간 데이터가 겨우 5일치이므로 O(n^2)이라고 해도 큰 차이는 없습니다. 위에서는 평일을 기준으로 설명했지만, 일반화해서 각 일별로 그룹을 만든다고 생각하면 됩니다.

arrival-result2

위의 표를 보면 전보다는 비교적 24번 버스의 시간표가 잘 정리된 것을 확인할 수 있습니다. 하지만 아직까지는 좀 더 데이터를 다듬어야 할 것 같네요. 몇 가지 규칙을 정하여 데이터를 솎아보았습니다.

배차 간격이 너무 짧거나 긴 경우

arrival-result3

배차 간격이 사실 제일 중요합니다. 시간표에는 특정 배차간격이 존재하기 때문입니다. 구간에 따라 약간 다르지만, 대체로 비슷합니다. 그래서 중간에 배차간격이 너무 짧거나 긴 데이터들을 삭제하거나 조정해야합니다.

위의 사진을 보시면, 다른 영역에서 7~10분이던 배차간격이 갑자기 3분으로 되어있죠? 이상 데이터인 것 같네요. 아래 시간과 비교해보니 거의 비슷한 시간대임을 알 수 있습니다. 10:58이라고 찍힌 버스들이 잘못 수집되었거나 특이한 경우이고, 11:01버스가 아래 행에 포함되면 자연스럽겠네요.

최소값과 평균의 차이가 너무 큰 경우

arrival-result4

그리고 같은 시간대 그룹에서 최소값, 평균의 차이가 너무 큰 경우도 조정해야합니다. 위의 예시에서는 10:07버스가 다른 데이터에 비해 너무 작습니다. 이렇게 최소값과 평균의 차이를 비교해보면 특이한 데이터들을 발견할 수 있습니다.

이렇게 한 그룹에서 특이한 데이터들은 잘못 수집되었거나, 그 버스만 특이하거나, 다른 그룹에 들어갔어야 할 데이터인 경우입니다. 상황에 맞춰 조정해주었습니다.

특정 시간대의 데이터가 너무 적은 경우

arrival-result5

21:32분대의 그룹에 시각이 1개뿐입니다. 저 데이터가 특이한 데이터라고 생각할 수 있습니다. 그래서 그냥 삭제하거나 윗 그룹 또는 아랫 그룹과 차이가 별로 안나면 합쳐주었습니다. 위의 예시에서는 특이한 데이터라고 생각할 수 있겠네요. 그래서 삭제하였습니다.

분단위로 시간표 계산

arrival-final-result

마지막으로 이렇게 정리된 시간표를 분단위로 평균내어 최종 시간표를 만들었습니다. 파란색 영역이 시간표입니다. 이렇게 만들어진 시간표를 이용하여 예측을 진행하였습니다.

시간표 적용

arrival-table

이제 도출한 시간표를 앱에 적용해야합니다. 시간표의 종류가 많고, 시간표마다 약간의 미세한 조정이 필요해서 파일로 만들어 체계적으로 관리하기로 했습니다.

테이블 파일은 아래와 같은 형태로 되어있습니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
# 720-3 time table

# 전체 Offset
@offset PT3M

# 시간표
# 문법: 시:분
7:00
8:30
10:00
11:30
13:00
14:20
15:40
17:00
18:30
20:00
21:30
23:00

문법은 대략 아래와 같습니다.

  • #로 시작하는 줄은 주석입니다.
  • @offset은 시간표에 적용할 오프셋입니다.
  • 맨 아래에 시각 목록을 작성합니다.

이 시간표를 적절히 파싱해서 웹 앱에서 사용할 수 있게 해야합니다.

예측하기

예측은 아래와 같은 간단한 알고리즘으로 이루어집니다.

  1. 실제 도착 정보가 존재하면, 그 도착 정보를 제공하기
  2. 실제 도착 정보가 없으면, 시간표를 이용하여 예측하기

하지만 경기버스API만 사용하는게 아니며, 버스도 여러 대이고, 정류소도 2개 종류이며, 관리해야하는 시간표도 공휴일/평일/토요일 등 여러 종류입니다.

그래서 실제로는 좀 더 복잡하게 구현되어있습니다. 아래는 제가 구상한 아키텍처입니다.

arrival-architecture

BusArrivalProvider는 실제 경기 버스나 외부 버스 API를 사용해 도착 정보를 가져오는 역할을 합니다. 물론 반드시 외부 API일 필요는 없고, 셔틀버스처럼 내부적으로 시간표에 따라 도착 정보를 가져올 수도 있습니다.

BusArrivalPredictService는 예측한 버스 도착 정보를 제공합니다. 인터페이스로 구성한 이유는 향후에 시간표가 아닌 다른 방식으로 예측할 수도 있기 때문입니다. 아직까지는 시간표를 통한 예측만 제공합니다.

그래서 실제 버스 도착 정보와 예측 버스 도착 정보를 합성해서 최종 버스 도착 정보를 제공합니다. 이 합성하는 과정은 위에서 설명한 알고리즘대로 흘러갑니다. 실제 도착 정보가 있으면 그 정보를 반환하지만, 없으면 예측한 정보를 반환합니다.

저희 앱에서는 이런 방식으로 버스 도착 정보를 제공합니다. 버스 도착 정보는 주기적으로 계산되어 Redis에 저장됩니다. 사용자는 Redis에서 꺼내어 응답을 받습니다. 그래서 시간별로 오차가 있을 수 있습니다. 예를 들어 30초주기로 버스 도착 정보를 redis에 저장한다면, 저장하고 20초뒤에는 예상 시간에서 20초를 빼주어야 정확할겁니다. 그래서 사용자가 api call할 때 redis에서 정보를 꺼내어 주면서 예상 시간에서 지난 시간만큼 빼주는 방식으로 구현하였습니다.

결론

데이터를 분석해본 일은 처음이라 쉽지 않았습니다. 분석해보면서 데이터 사이의 공통점을 발견하고, 시간표를 추측하는 과정이 재미있었던 것 같습니다. 분석하며 알아낸 정보들을 바탕으로, 시간표를 자동으로 만들어주어 주기적으로 업데이트해주는 pipeline을 구성하면 좋을 것 같네요.

This post is licensed under CC BY 4.0 by the author.

[Spring] 게시판 기능 설계

[Server] 무중단 배포