Notice
Recent Posts
Recent Comments
Link
«   2025/01   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
Archives
Today
Total
관리 메뉴

개발자되기 프로젝트

[Server] 실시간 지하철 도착정보 연동 본문

Project/대중교통 길찾기

[Server] 실시간 지하철 도착정보 연동

Seung__ 2022. 2. 10. 21:16

1. 실시간 지하철 도착 정보


각 경로에는 지하철도 포함되어 있다.

 

버스 뿐 만 아니라 지하철의 실시간 정보를 받아오자.

 

서울 데이터 광장에서 인증키를 받으면 실시간 지하철 도착정보를 사용할 수 있다.

key를 발급받은 후 다음과 같이 apikey와 역 이름만 전달하면 값을 데이터를 받을 수 있다.

http://swopenAPI.seoul.go.kr/api/subway/{apiKey}/json/realtimeStationArrival/0/10/{역이름}
변수명 타입 변수 설명 값 설명
KEY String(필수) 인증키 OpenAPI 에서 발급된 인증키
TYPE String(필수) 요청파일타입 xml : xml, xml파일 : xmlf, 엑셀파일 : xls, json파일 : json
SERVICE String(필수) 서비스명 realtimeStationArrival
START_INDEX INTEGER(필수) 요청시작위치 정수 입력 (페이징 시작번호 입니다 : 데이터 행 시작번호)
END_INDEX INTEGER(필수) 요청종료위치 정수 입력 (페이징 끝번호 입니다 : 데이터 행 끝번호)
statnNm STRING(필수) 지하철역명 지하철역명

 

return받는 데이터는 다음과 같은 구조이다. 설명은 명세서 참고.

{
    "errorMessage": {
        "status": 200,
        "code": "INFO-000",
        "message": "정상 처리되었습니다.",
        "link": "",
        "developerMessage": "",
        "total": 13
    },
    "realtimeArrivalList": [
        {
            "beginRow": null,
            "endRow": null,
            "curPage": null,
            "pageRow": null,
            "totalCount": 13,
            "rowNum": 1,
            "selectedCount": 5,
            "subwayId": "1004",
            "subwayNm": null,
            "updnLine": "상행",
            "trainLineNm": "당고개행 - 총신대입구(이수)방면",
            "subwayHeading": "오른쪽",
            "statnFid": "1004000434",
            "statnTid": "1004000432",
            "statnId": "1004000433",
            "statnNm": "사당",
            "trainCo": null,
            "ordkey": "01000당고개0",
            "subwayList": "1002,1004",
            "statnList": "1002000226,1004000433",
            "btrainSttus": null,
            "barvlDt": "10",
            "btrainNo": "4330",
            "bstatnId": "0",
            "bstatnNm": "당고개",
            "recptnDt": "2022-02-10 20:41:22.0",
            "arvlMsg2": "사당 도착",
            "arvlMsg3": "사당",
            "arvlCd": "1"
        }
    ]
}

 

 

2. API 연동 계획


여기서 중요한 점은, 길찾기 API와 연동이 가능해야 한다는 것이다.

 

현재 구조는 ODsay를 통해 경로를 요청하고 지하철 탑승의 경우 지하철 도착 정보를 요청한다.

 

이 때 ODsay에서 넘어온 값으로 지하철 API를 요청해야 한다. 

 

같은 종류에 데이터에 대해 각 API가 사용하는 값이 다르므로 둘을 매칭할 필요가 있다. 

  odsay seoul
지하철 호선 이름 String name  "수도권 2호선"  
지하철 호선 int subwayCode 2  String subwayId " 1077"
방면 String way  "잠실" String trainLineNm "-1행 - 시청방면"
도착지 방면
상하행 wayCode updnLine "상행", "하행"
(지하철의 첫번째 경로에만 필수)
1 : 상행, 2: 하행
2호선(1:외선, 2:내선)
(2호선 : (내선,외선),상행,하행)

특히 올바른 안내를 위해 호선 정보와  방면 정보가 중요하다.

 

위의 데이터 중에서 "방면"정보는 사용이 어렵다.

왜냐? 지하철 API의 방면정보는 종점 기준이나, ODsay는 그렇지 않다. 사용하기가 번거롭다.

 

따라서 방면정보 대신에 상하행 정보를 사용하겠다. 둘 다 String이라 오히려 좋아.

(Map으로 만들어서 필요할 때 마다 불러서 사용.)

 

아래와 같이 api별 지하철 호선 정리했다. 이 값들은 변하지 않아서 사용하기 용이하다.

(Map으로 만들어서 필요할 때 마다 불러서 사용.)

호선 공공api odsay
type String int
변수 명 subwayId subwayCode
1호선 1001 1
2호선 1002 2
3호선 1003 3
4호선 1004 4
5호선 1005 5
6호선 1006 6
7호선 1007 7
8호선 1008 8
9호선 1009 9
공항철도 1065 101
경의중앙선 1063 104
경춘선 1067 108
신분당선 1077 109

 

 

3. SeoulClient

위의 계획을 기반으로 코드를 작성했다.

@Slf4j
@Component
public class SeoulClient {

    @Value(("${seoul.uri}"))
    private String uri;
    private JSONObject jsonResult;
    private Map<String, ErrorType> errorTypeMap;

    public SeoulSubwayArrivalInfoRes getRealtimeInfo(String stationName, String subwayId, String updnLine) {

        String uriString = UriComponentsBuilder.fromUriString(uri+stationName).build().toUriString();

        URI uri = UriComponentsBuilder.fromUriString(uriString).encode().build().toUri();

        log.info("[request seoul-subway api] uri = {}", uri);
        //Http Entity
        var httpEntity = new HttpEntity<>(new HttpHeaders());
        var responseType = new ParameterizedTypeReference<String>(){};

        try{
            //ResponseEntity
            var responseEntity= new RestTemplate().exchange(
                    uri, HttpMethod.GET, httpEntity, responseType
            );
            jsonResult = new JSONObject(responseEntity.getBody());

            log.info("[SeoulClient] Station Name: {}", stationName);
            errorCheck(jsonResult);

        }catch (HttpClientErrorException.BadRequest | HttpServerErrorException.InternalServerError exception){
            log.info("[Google Client] Exception 발생: {}", exception.getMessage());
            JSONObject errorInfo = new JSONObject(exception.getResponseBodyAsString());

            errorCheck(errorInfo);
        }
        JSONArray arrivalList = jsonResult.getJSONArray("realtimeArrivalList");
        for (int i=0; i<arrivalList.length(); i++) {
            JSONObject arrival = arrivalList.getJSONObject(i);
            if (isSameWay(subwayId, updnLine, arrival)){
                return new SeoulSubwayArrivalInfoRes((String) arrival.get("arvlMsg2"), (String) arrival.get("trainLineNm"));
            }

        }

        return new SeoulSubwayArrivalInfoRes("요청하신 방향으로 운행이 종료되었습니다.", updnLine);

    }

    private boolean isSameWay(String subwayId, String updnLine, JSONObject arrival) {
        if (subwayId.equals(arrival.get("subwayId"))){
            //같은 호선일 때 다음 역이 같아야 같은 방면임.
            if (updnLine.equals(arrival.get("updnLine"))){
                return true;
            }
        }
        return false;
    }

    private void errorCheck(JSONObject jsonResult) {
        int status=0;
        String code="";
        String message="";

        if (!jsonResult.isNull("errorMessage")){
            JSONObject jsonErrorMessageObject = jsonResult.getJSONObject("errorMessage");
            code = (String) jsonErrorMessageObject.get("code");
            message = (String) jsonErrorMessageObject.get("message");
        }else {
            code = (String) jsonResult.get("code");
            message = (String) jsonResult.get("message");
        }

        log.info("[SeoulClient] Error code: {}", code);
        log.info("[SeoulClient] Error Message: {}", message);
        if (errorTypeMap.get(code) == INVALID_KEY){
            throw new APIKeyException(message);
        }else if (errorTypeMap.get(code) == NO_DATA) {
            throw new NoResultException(message);
        } else if (errorTypeMap.get(code) == USER){
            throw new IllegalArgumentException(message);
        }else if (errorTypeMap.get(code) == SERVER){
            throw new APIServerException(message);
        }else if (errorTypeMap.get(code) == DB){
            throw new APIServerException(message);
        }

    }



    @PostConstruct
    private void setErrorType(){
        errorTypeMap = new LinkedHashMap<>();
        errorTypeMap.put("INFO-000", OK);
        errorTypeMap.put("INFO-100", INVALID_KEY);
        errorTypeMap.put("INFO-200", NO_DATA);
        errorTypeMap.put("ERROR-300", USER);
        errorTypeMap.put("ERROR-301", USER);
        errorTypeMap.put("ERROR-331", USER);
        errorTypeMap.put("ERROR-332", USER);
        errorTypeMap.put("ERROR-333", USER);
        errorTypeMap.put("ERROR-334", USER);
        errorTypeMap.put("ERROR-335", USER);
        errorTypeMap.put("ERROR-336", USER);
        errorTypeMap.put("ERROR-500", SERVER);
        errorTypeMap.put("ERROR-600", DB);
        errorTypeMap.put("ERROR-601", DB);
    }

}

 

그렇다면 SeoulClient를 호출은 언제 해야할까?

버스와 같이 subPath 객체가 생성될 때 호출하도록 했다.

@Autowired
public void getStationIdAndRealTimeInfo(OdSayClient odSayClient, SeoulClient seoulClient) {

    if (this.trafficType == 2){
        this.stationId = odSayClient.getStationId(startName, String.valueOf(startX), String.valueOf(startY));
        SearchRealTimeStationReq searchRealTimeStationReq = new SearchRealTimeStationReq(stationId, this.lane.getBusID());
        SearchRealTimeStationRes realTimeBusInfo = odSayClient.getRealTimeBusStation(searchRealTimeStationReq);
        this.arrivalMin = realTimeBusInfo.getArrivalMin();
        this.leftStation = realTimeBusInfo.getLeftStation();
    }else if(this.trafficType == 1){
        int odsaySubwayCode = this.lane.subwayCode;

        if (seoulSubwayIdMap.containsKey(odsaySubwayCode)){
            //노선 id odsaty -> seoul
            String seoulSubwayId = seoulSubwayIdMap.get(odsaySubwayCode);

            //진행방향 : 상행, 하행, 외선, 내선 확인
            if (seoulSubwayId.equals("1002")){
                updnLine = updnLine2ndMap.get(wayCode);
            }else {
                updnLine = updnLineMap.get(wayCode);
            }

            SeoulSubwayArrivalInfoRes arrivalInfoRes = seoulClient.getRealtimeInfo(startName, seoulSubwayId, updnLine);
            this.arrivalMessage = arrivalInfoRes.getArrivalMessage();
        }
    }
}

 

 

4. 결과


지하철 실시간 도착 정보가 추가되었다.

 

 

흠 그런데 api에서 자주 에러가 발생한다....

{
    "status": 500,
    "code": "INFO-200",
    "message": "해당하는 데이터가 없습니다.",
    "link": "",
    "developerMessage": "",
    "total": 0
}

 

 

5. GitHub: 220210 Subway API


 

GitHub - bsh6463/commuteMap

Contribute to bsh6463/commuteMap development by creating an account on GitHub.

github.com

 

'Project > 대중교통 길찾기' 카테고리의 다른 글

API 로 변경  (0) 2022.03.27
[Server] ODsay 에러 처리  (0) 2022.02.10
Field값 검증  (0) 2022.02.07
[Server] Exception Resolver  (0) 2022.02.05
[Server] Google Geocoding Error처리  (0) 2022.02.05
Comments