버스정류소어때 앱은 처음에는 현재 위치나 지역명을 기준으로 주변 버스정류소를 찾는 작은 생활 편의 앱으로 시작했습니다.
이번 개편에서는 기존 정류소 조회 기능을 중심으로 버스노선, 고속버스, 지하철, 지도, 위치 보완 기능을 확장하여 생활 교통 정보를 한곳에서 확인할 수 있는 형태로 발전시켰습니다.
이번 글에서는 버스정류소어때 개편에 사용한 API와 기술 구현 방식을 개발 관점에서 정리해 보겠습니다.
1. 개발 환경과 기본 방향
이번 개편은 기존 앱 구조를 최대한 유지하면서 기능을 확장하는 방식으로 진행했습니다.
개발 환경은 다음과 같습니다.
- .NET MAUI
- C#
- Android
- Shell 기반 하단 탭 구조
- WebView 기반 지도 표시
- 공공데이터 API 연동
- 내부 JSON 데이터 저장 구조
기존 프로젝트를 완전히 새로 만들기보다는, 화면별 Page와 기능별 Service를 추가하는 방식으로 개편했습니다.
앱 전체 구조를 크게 흔들지 않고 필요한 기능을 단계적으로 붙이는 방향이었습니다.
2. 전체 화면 구조
이번 개편에서는 하단 탭을 중심으로 앱 구조를 정리했습니다.
주요 탭은 다음과 같습니다.
- 홈
- 버스노선
- 고속버스
- 지하철
- 안내
각 탭은 AppShell.xaml에서 ShellContent로 연결했습니다.
화면 구성은 대략 다음과 같습니다.
MainPage : 홈 / 정류소 검색
BusRoutePage : 주변 버스노선 조회
ExpressBusPage : 고속버스 조회
SubwayPage : 지하철 조회
GuidePage : 안내 화면
하단 탭에는 기능 성격에 맞는 아이콘을 적용하여 사용자가 원하는 기능으로 바로 이동할 수 있도록 구성했습니다.
3. 버스정류소와 버스노선 API
버스정류소와 버스노선 정보는 국토교통부 TAGO 공공데이터 API를 중심으로 구성했습니다.
주요 기능은 다음과 같습니다.
- 정류소 검색
- 현재 위치 기준 주변 정류소 조회
- 정류소 경유 노선 조회
- 버스번호 기준 노선 검색
- 버스 위치 조회
- 도착 정보 조회
서비스 구현은 주로 BusStopService.cs에서 처리했습니다.
대표 메서드는 다음과 같습니다.
GetNearbyStopsAsync
- 현재 위치 또는 검색 위치 기준 주변 정류소 조회
SearchStopsByKeywordAsync
- 지역명, 정류소명, 역명, 터미널명 기준 정류소 검색
GetRoutesByStopAsync
- 선택한 정류소를 경유하는 버스노선 조회
SearchRoutesByRouteNoAsync
- 버스번호 기준 노선 검색
GetBusPositionsAsync
- 선택한 노선의 현재 버스 위치 조회
버스 관련 API는 공식 코드와 명칭이 중요합니다.
하지만 사용자는 공식 명칭보다 자연스러운 이름으로 검색하는 경우가 많습니다.
예를 들어 다음과 같은 입력이 가능합니다.
강남역
하단역
서울경부터미널
부산터미널
이런 검색어가 API에서 바로 매칭되지 않을 수 있기 때문에 검색어 후보를 확장했습니다.
강남역 → 강남
하단역 → 하단
서울경부터미널 → 서울경부
부산터미널 → 부산
이렇게 여러 후보로 다시 조회하도록 구성하여 검색 실패를 줄였습니다.
4. 홈 화면 통합 검색
홈 화면은 검색창 하나로 여러 기능을 처리하도록 만들었습니다.
사용자가 입력한 값에 따라 검색 흐름을 다르게 처리합니다.
숫자만 입력 → 버스번호 검색
문자 입력 → 위치명 또는 정류소명 검색
좌표 변환 성공 → 주변 정류소 조회
좌표 변환 실패 → 정류소명 직접 검색
전체 흐름은 다음과 같습니다.
검색어 입력
→ 숫자인지 확인
→ 숫자면 버스노선 검색
→ 문자인 경우 위치 좌표 변환
→ 좌표가 있으면 주변 정류소 조회
→ 좌표가 없으면 정류소명 직접 검색
→ 결과 표시
좌표 변환에는 MAUI 기본 Geocoding과 VWorld 검색 API를 함께 사용했습니다.
즉, 하나의 검색창에서 정류소 검색, 지역 검색, 버스번호 검색을 모두 처리할 수 있도록 구성했습니다.
5. 지도 표시 방식
지도는 앱 내부 WebView에 HTML을 생성하여 표시하는 방식으로 구현했습니다.
지도 생성은 BusMapWebViewHelper.cs에서 담당합니다.
기술 구성은 다음과 같습니다.
- WebView
- OpenLayers
- VWorld WMTS 지도
- OSM fallback 지도
- 마커 표시
- 노선 라인 표시
기본 지도는 VWorld를 사용하고, VWorld 타일 로딩이 실패할 경우 OSM 지도로 전환할 수 있도록 보완했습니다.
지도에서 표시하는 주요 대상은 다음과 같습니다.
정류소 위치
버스 위치
버스노선 전체 경로
지하철역 위치
고속버스 현재 위치
터미널 주변 정류소
마커도 용도별로 구분했습니다.
stop : 정류소 또는 역 위치
bus : 버스 또는 주변 정류소
start : 시작 지점
end : 종료 지점
selectedBus : 선택한 버스
지도는 외부 타일 서버와 WebView에 의존하기 때문에 실패 가능성이 있습니다.
그래서 지도 로딩 중 오류가 발생해도 앱이 종료되지 않도록 예외 처리를 강화했습니다.
6. 고속버스 API 구현
고속버스는 국토교통부 TAGO 고속버스 도착정보 API를 사용했습니다.
담당 서비스는 ExpressBusService.cs입니다.
주요 기능은 다음과 같습니다.
GetTerminalsAsync
- 터미널 목록 조회
GetArrivalTerminalsAsync
- 출발 터미널 기준 도착 가능한 터미널 조회
GetArrivalInfoResultAsync
- 출발/도착 터미널 기준 고속버스 도착정보 조회
고속버스 API는 시내버스 API와 달리 터미널 코드 중심으로 동작합니다.
따라서 단순히 서울, 부산 같은 이름만으로는 정확한 조회가 어려울 수 있습니다.
예를 들어 서울도 다음처럼 여러 터미널로 나뉩니다.
서울경부
동서울
센트럴시티
서울남부
그래서 앱에서는 출발터미널을 먼저 선택하고, 해당 출발지에서 도착 가능한 터미널 목록을 다시 조회하는 방식으로 구성했습니다.
흐름은 다음과 같습니다.
출발터미널 선택
→ 출발터미널 코드 확보
→ 도착 가능한 터미널 목록 조회
→ 도착터미널 선택
→ 도착정보 자동 조회
또한 출발터미널과 도착터미널 주변의 버스정류소를 바로 조회할 수 있도록 버튼을 추가했습니다.
고속버스 이용 전후에 시내버스로 이동해야 하는 경우를 고려한 기능입니다.
7. 고속버스 위치 표시
고속버스 도착정보에는 현재 위치명이 포함되는 경우가 있습니다.
예를 들면 다음과 같습니다.
천안휴게소
동탄JC
원지동489-2
하지만 이런 위치명은 항상 좌표를 포함하지는 않습니다.
그래서 별도의 위치 변환 로직을 구성했습니다.
관련 파일은 다음과 같습니다.
ExpressBusLocationResolver.cs
ExpressBusMapPage.xaml.cs
처리 방식은 다음과 같습니다.
API에 좌표가 있으면 그대로 사용
좌표가 없고 위치명이 있으면 장소명 검색
좌표 변환 성공 시 지도 표시
실패 시 사용자 메시지 표시
고속버스 위치 정보는 API 제공 범위에 따라 정확도가 달라질 수 있기 때문에, 실패 시 앱이 종료되지 않고 안내 문구를 표시하도록 처리했습니다.
8. 지하철 API 구현
지하철 정보도 국토교통부 TAGO 지하철정보 API를 사용했습니다.
담당 서비스는 SubwayService.cs입니다.
주요 기능은 다음과 같습니다.
- 지하철역 검색
- 역별 버스노선 조회
- 역 주변시설 조회
- 역 시간표 조회
처음에는 도시나 호선을 선택할 때마다 API를 직접 호출하는 방식이었습니다.
하지만 API 호출 속도와 역 누락 문제가 있어 구조를 변경했습니다.
현재 방식은 다음과 같습니다.
최초 전체 지하철역 데이터 수집
→ 앱 Raw 리소스에 seed JSON 저장
→ 앱 실행 후 내부 DB로 복사
→ 도시/호선/역 조회는 내부 DB 우선 사용
→ 필요 시 지하철정보 가져오기 버튼으로 최신 데이터 재수집
초기 데이터 파일은 다음과 같습니다.
Resources/Raw/subway_station_seed.json
내부 저장은 SubwayDataStore.cs에서 처리합니다.
저장 위치는 다음과 같습니다.
FileSystem.AppDataDirectory/subway_station_db.json
SQLite를 추가하지 않고 JSON 기반 내부 DB로 처리했습니다.
지하철역 데이터 구조가 비교적 단순하고, 기존 앱 구조에 최소 변경으로 붙이기 좋았기 때문입니다.
9. 지하철 도시, 호선, 역 분류
지하철 API에서 제공하는 호선명은 항상 도시명을 포함하지 않습니다.
예를 들어 다음과 같은 값이 있습니다.
1호선
2호선
3호선
동해
수인분당
부산김해경전철
단순히 문자열에 부산, 서울 같은 단어가 들어 있는지만으로 도시를 분류하면 누락이 발생합니다.
그래서 다음 기준을 함께 사용했습니다.
- StationId 접두어
- 호선명
- 도시별 route keyword
예시는 다음과 같습니다.
MTRBS → 부산
MTRDG → 대구
MTRDJ → 대전
MTRGJ → 광주
MTRICI → 인천
MTRS → 서울/수도권
이렇게 처리하여 부산진, 서면, 반월당처럼 단순 호선명만 가진 역도 올바른 도시로 분류되도록 했습니다.
10. 지하철 역 검색 개선
초기에는 호선을 선택해야만 해당 호선 안에서 역 검색이 가능했습니다.
하지만 실제 사용자는 호선을 정확히 모르는 경우가 많습니다.
그래서 역 검색 방식을 도시 전체 기준으로 변경했습니다.
현재 방식은 다음과 같습니다.
도시 선택
→ 호선 선택 가능
→ 검색어 입력 시 선택 호선과 관계없이 도시 전체 역에서 검색
예를 들어 부산을 선택하고 하단을 검색하면, 선택된 호선과 관계없이 부산 전체 역 중 하단역을 찾습니다.
역 목록 정렬은 단순 이름순이 아니라 역 ID 순번 기반으로 처리했습니다.
가능한 한 시작역에서 종점역 방향에 가깝게 보이도록 하기 위한 방식입니다.
11. 지하철역 지도 보완
지하철역명을 선택하면 지도 화면을 표시합니다.
담당 화면은 SubwayStationMapPage.xaml.cs입니다.
처음에는 역명으로 VWorld 장소 검색을 해서 좌표를 찾았습니다.
하지만 일부 역명은 지도 API에서 정확히 잡히지 않는 경우가 있었습니다.
그래서 다음 fallback 구조를 추가했습니다.
1. 역명으로 장소 검색
2. 실패하면 “역” 제거 후 재검색
3. 그래도 실패하면 주변 버스정류소 검색
4. 정류소 좌표를 기준으로 지도 표시
5. 주변 버스정류소 마커도 함께 표시
즉, 역 자체 좌표가 바로 조회되지 않아도 주변 교통 정보를 기준으로 지도를 표시할 수 있게 했습니다.
12. 지하철 주변 정보 연계
지하철역 화면에는 다음 기능 버튼을 구성했습니다.
- 버스노선
- 주변시설
- 시간표
- 지도
역별 버스노선 API가 일부 지역에서 불안정하거나 응답이 없는 경우가 있어, 버스노선 버튼은 주변 버스정류소 검색 방식으로 보완했습니다.
처리 흐름은 다음과 같습니다.
지하철역 버스노선 버튼
→ 홈 화면으로 이동
→ 역명 기준 주변 정류소 조회
→ 정류소 목록 표시
→ 사용자가 정류소 선택 시 노선 조회
API 하나에만 의존하지 않고, 기존 정류소 조회 기능과 연결하여 정보를 보완하는 방식입니다.
13. 내부 DB와 캐시 구조
이번 개편에서 새로 들어간 내부 데이터 저장 구조는 지하철역 데이터입니다.
담당 파일은 SubwayDataStore.cs입니다.
역할은 다음과 같습니다.
앱에 포함된 seed JSON 읽기
앱 내부 DB 파일 읽기
API에서 새로 받은 역 목록 병합
추가/변경분 저장
첫 실행 흐름은 다음과 같습니다.
앱 첫 실행
→ Raw seed JSON 읽기
→ AppDataDirectory에 내부 DB 저장
최신 데이터 갱신 흐름은 다음과 같습니다.
지하철정보 가져오기 버튼
→ API 전체 역 목록 조회
→ 기존 내부 DB와 비교
→ 추가/변경분 병합
→ 내부 DB 저장
데이터 비교 키는 다음 항목을 기준으로 했습니다.
StationId
StationName
RouteName
이 구조를 통해 매번 API를 호출하지 않고도 빠르게 지하철역 목록을 표시할 수 있습니다.
14. 예외 처리와 앱 안정성
이번 개편에서 가장 중요하게 본 부분은 앱이 오류로 종료되지 않게 하는 것입니다.
공공데이터 API는 네트워크 상태, 시간대, 지역, 노선, 터미널 코드에 따라 응답이 없거나 지연될 수 있습니다.
그래서 다음 영역을 집중적으로 보완했습니다.
- 앱 시작
- Shell 화면 생성
- Syncfusion 라이선스 등록
- API 호출
- 네트워크 실패
- 지도 좌표 변환 실패
- 터미널 선택 화면
- 외부 링크 실행
- 지하철 데이터 저장/읽기
대표적인 처리 방식은 다음과 같습니다.
try
{
// API 호출 또는 화면 이동
}
catch (Exception ex)
{
// 앱 종료 대신 상태 문구 표시
SetStatus("조회 오류: " + GetSafeMessage(ex));
}
또한 전역 예외 처리도 추가했습니다.
AppDomain.CurrentDomain.UnhandledException
TaskScheduler.UnobservedTaskException
앱 시작 중 오류가 발생해도 완전히 종료되지 않도록 기본 안내 화면을 표시하는 fallback도 구성했습니다.
15. API 응답 처리 기준
공공데이터 API에서는 다음 상황이 자주 발생할 수 있습니다.
응답 없음
데이터 없음
resultCode 오류
HTTP 오류
타임아웃
XML 구조 차이
특정 지역/노선 미제공
서비스 계층에서는 다음을 처리했습니다.
HTTP 상태 코드 확인
빈 응답 확인
XML 파싱 오류 처리
resultCode/resultMsg 확인
TaskCanceledException 타임아웃 처리
HttpRequestException 네트워크 오류 처리
사용자에게는 기술적인 오류 내용을 그대로 보여주지 않고, 가능한 한 짧고 이해하기 쉬운 안내 문구로 표시하도록 했습니다.
16. 지도 fallback 구조
지도는 외부 지도 타일과 JavaScript에 의존합니다.
따라서 지도 자체도 실패할 수 있습니다.
BusMapWebViewHelper.cs에는 다음 처리를 넣었습니다.
VWorld 일반 지도 로딩
VWorld 위성 지도 로딩
타일 로딩 실패 감지
일정 횟수 실패 시 OSM 지도 전환
지도 스크립트 오류 표시
WebView 안의 JavaScript에서도 오류 메시지를 화면에 표시하도록 구성했습니다.
지도 오류가 앱 전체 종료로 이어지지 않도록 하는 것이 핵심입니다.
17. 화면 UX 개선
이번 개편에서는 작은 화면의 휴대폰과 큰 글자 설정도 고려했습니다.
적용한 방식은 다음과 같습니다.
ScrollView 사용
FlexLayout으로 버튼 줄바꿈
긴 문구 WordWrap
버튼 HeightRequest 확보
하단 탭 아이콘 적용
상태 문구 분리
선택 화면 색상 적용
특히 지하철 호선 버튼은 한 줄 가로 스크롤만으로는 일부 호선이 잘 보이지 않을 수 있어 여러 줄로 표시되도록 변경했습니다.
검색 화면, 터미널 선택 화면, 요청 화면도 가능한 한 단순하고 명확하게 보이도록 정리했습니다.
18. 배포 빌드
Google Play 업로드용 파일은 Release 모드의 AAB 형식으로 생성했습니다.
현재 버전 정보는 다음과 같습니다.
ApplicationDisplayVersion: 1.0.10
ApplicationVersion: 10
ApplicationId: com.eplus.ebusstop
빌드 형식은 다음과 같습니다.
TargetFramework: net9.0-android
Configuration: Release
PackageFormat: aab
Signed: true
산출물 예시는 다음과 같습니다.
C:\eAPP\eBusStop\bin\Release\net9.0-android\publish\com.eplus.ebusstop-Signed.aab
19. 정리
이번 버스정류소어때 개편은 단순히 화면을 몇 개 추가한 작업이 아닙니다.
기존 버스정류소 조회 앱을 생활 교통 통합 조회 앱으로 확장한 작업입니다.
핵심 기술 방향은 다음과 같습니다.
- 공공데이터 API 직접 연동
- 버스정류소, 버스노선, 고속버스, 지하철 정보 통합
- WebView 기반 지도 표시
- VWorld 지도와 OSM fallback
- 지하철역 내부 JSON DB 캐시
- 검색어 후보 확장
- 터미널/역명 주변 정류소 연계
- API 오류와 네트워크 오류 방어 처리
- Shell 기반 하단 탭 구성
- 작은 화면과 큰 글자 설정을 고려한 UX 보완
전체적으로는 “공식 코드 기반 조회”와 “사용자가 자연스럽게 입력하는 이름 기반 조회” 사이의 차이를 줄이는 데 초점을 맞췄습니다.
공공데이터 API는 정확한 코드와 명칭을 요구하는 경우가 많지만, 실제 사용자는 강남역, 서울터미널, 부산, 102번처럼 자연스럽게 입력합니다.
이번 개편은 그 간극을 줄이기 위한 작업이었습니다.
앞으로도 버스정류소어때는 실제 이동 중 필요한 교통 정보를 더 쉽고 빠르게 확인할 수 있는 앱으로 계속 개선해 나갈 예정입니다.
조그만 기술로 세상을 이롭게.
이플러스가 계속 만들어가겠습니다.
'조그만 기술로 세상을 이롭게 > 버스정류소어때' 카테고리의 다른 글
| 버스정류소어때 개편 안내 (1.0.11) - 시외버스 추 (0) | 2026.07.07 |
|---|---|
| 버스정류소어때 개편 안내 (1.0.10) (1) | 2026.07.07 |
| 버스정류소어때 앱 개선 안내 (0) | 2026.06.01 |
| 버스정류소어때 구글플레이스토어 출시 안내 (0) | 2026.05.28 |
| 버스정류소어때 앱 기능 및 개편 내용 소개 (0) | 2026.05.27 |