구름톤 유니브 3기를 진행하면서 단풍톤이라는 해커톤에도 나가보게 되었다. 이에 대한 회고록은 나중에 작성해보고 단풍톤을 준비하면서 새롭게 알게되었던 것들을 차례로 적어보는 시간을 가져볼까 한다.
우선 우리 서비스는 교통약자를 위한 길찾기 서비스로, 교통약자를 위한 경로 제공 기능을 구현하기 위해 여러 지도 API를 찾아보게 되었다.
아무래도 카카오와 구름이 주관하는 행사이다보니 카카오 API를 한 개 이상 사용하는 것이 평가항목이라 이걸 쓰면 되겠다 싶었는데,,

네?
아무래도 카카오에서는 자동차 경로만 API로 제공하는 것 같았다. 그래도 우리에게는 킹갓 네이버가 남아있으니까 바로 네이버 경로 API를 검색해보았다. 
ㅋㅋ불안한 이 기분,,
위 url을 타고 들어가 확인해보니 네이버에서는 URL Scheme으로만 제공해주는 것 같았다. 사실 해커톤이다보니 개발기간이 짧아 경로를 화면에 띄우는 방식 대신 경로 설명 텍스트를 받아와 화면에 한 줄 한 줄 출력해주면서 음성으로 변환할 계획이었기 때문에 네이버 API도 우리와는 맞지 않았다.
그렇게 계속 삽질을 하다가 발견한 TMAP API!!
평소에 tmap을 네비게이션 용도로만 사용해서 그런지 당연히 대중교통 API는 제공하지 않을 줄로만 알았는데

사랑해요 티맵 ㅜㅜ
https://transit.tmapmobility.com/docs/routes
위 url로 이동하면 대중교통 api에 요청을 할 때 필요한 request parameter와 응답을 받을 때 얻을 수 있는 response parameter에 대한 설명을 볼 수 있다.
호출하기 전에 간단하게 요청 parameter를 확인해보자.
대중교통 경로 API Request Parameter
Mandatory라고 되어있는 부분은 무조건 포함되어야 하는 매개변수이고 요청의 params로 넣어주면 된다.{ "startX": "127.02479803562213", "startY": "37.504585233865086", "endX": "127.03747630119366", "endY": "37.479103923078995", "count" : 1, "lang": 0, "format":"json" }위 코드는 요청 예시이다.
아무튼 서비스 구현에 필요한 내용을 TMAP 측에서 전부 제공해주는 것을 확인했으므로 바로 Node.js를 사용해 연결해보겠다.
exports.findTransitRoute = async (req, res) => {
const { userId, startX, startY, endX, endY } = req.body;
if (!userId || !startX || !startY || !endX || !endY) {
return res
.status(StatusCodes.BAD_REQUEST)
.json({ message: 'userId 또는 좌표가 필요합니다' });
}
// ...중간 과정 생략
res.status(StatusCodes.OK).json({
message: '경로 탐색 성공',
routes: routeData,
});
} catch (err) {
console.error(err);
res.status(StatusCodes.INTERNAL_SERVER_ERROR).json({
message: '경로 탐색 중 서버 오류',
});
}
};
위 코드는 경로 조회 API에 해당하는 controller 부분이다. api 요청을 어떻게 일으켰는지에 대해 알아보기 위한 게시글이므로 자세한 로직은 생략하였다. 우선, 클라이언트의 요청 본문에서 시작장소 위도, 경도와 도착장소 위도, 경도를 받아온 후, 그 값들을 service layer로 넘겨주었다.
exports.fetchTransitRoute = async (coordinate) => {
try {
const response = await axios.post(
'https://apis.openapi.sk.com/transit/routes',
{
startX: coordinate.startX,
startY: coordinate.startY,
endX: coordinate.endX,
endY: coordinate.endY,
count: 3,
},
{
headers: {
appKey: process.env.TMAP_KEY,
},
}
);
const routeData = response.data.metaData.plan.itineraries;
// 경로 데이터 가공
const processedRoutes = routeData.map((itinerary) => ({
routeId: new mongoose.Types.ObjectId(),
type: itinerary.type,
route: itinerary.route,
// ... 중간 과정 생략
return processedRoutes;
} catch (err) {
console.error(err.message);
throw new Error('경로 fetch 실패');
}
};
axios의 post 메서드로 해당 url에 요청을 보낸다. count는 api 사용량을 제한하기 위해 3개로만 서버 단에서 설정해놓았다. 참고로 header에 tmap에서 발급해준 AppKey를 넣어줘야만 응답을 받을 수 있으니 잊지말자!

이렇게 postman에 params로 필수값들을 넣어주고 POST 요청을 보내보면

쨘~~!! 하고 응답값이 나온다. 사실 저게 응답값의 원본은 아니고 서비스 구현 시에 필요한 정보들만 가공한 것이다. 원본 값이 알고 싶으면 위에 첨부해놓은 url로 이동해 공식문서를 한 번 읽어보는 것을 추천한다.
저 응답값을 가공하는데 map() 메서드 때문에 상당히 애를 먹었었는데 map() 메서드도 설명할 겸, 조만간 api관련 로직 설명글도 작성해볼 예정이다.