카카오 모빌리티 API를 써보자..!

하영·2025년 3월 10일

React

목록 보기
24/26

카카오 모빌리티 API 사용하기

들어가며

카카오 지도 API를 활용해서 구현한 코드나 초기 설정 방법? 같은 건 많이 찾아볼 수 있는데,
카카오 모빌리티 API 에 관한 포스팅은 별로 없어서 조금 어려웠다.

사용자가 현재 위치에서 중간에 경유지를 한번 거친 후 목적지에 간다고 가정할 때의 예상 소요 시간과 최단거리 등을 나타내는 기능을 구현했다.

PoC 형태로 실현여부를 판단하기 위한 작업이라 구체적인 기능이나 스토리보드는 딱히 없었다...😣
(해야하니까 하는 것... 난 로봇이다 🤖)
아무튼 카카오 모빌리티 API에서 어떻게 데이터를 사용하고 컴포넌트, 함수 등을 간단하게 정리해보면서 복습해보기로 했다.

엄.. 이렇게 구현하는게 맞나 싶지만..! 잘못한 건 따로 공부하면서 수정해보기로! 😗


1. kakaoRoute 컴포넌트

fetchRoute 함수는 카카오모빌리티 API의 v1/directions 엔드포인트를 사용하여 경로 탐색을 수행하는 기능을 담당한다.

fetchRoute 함수 역할

  • 사용자가 출발지, 경유지, 목적지를 지정하면 카카오모빌리티 API를 호출하여 최적 경로를 탐색
  • API 응답 데이터를 활용해 경로 좌표, 예상 이동 거리, 소요 시간을 반환하는 함수
  • 최단 거리(DISTANCE)와 최단 시간(TIME) 중 사용자가 선택 가능하도록 구현함

1-1. API 호출 준비

const KAKAO_REST_API_KEY = import.meta.env.VITE_KAKAO_REST_API_KEY;
const WAYPOINTS_URL = "https://apis-navi.kakaomobility.com/v1/directions";

카카오 모빌리티 API 사이트를 참고해서 “다중 경유지 길찾기” 엔드포인드를 활용했다.

  • .env 환경변수에서 카카오 REST API 키를 가져오고
  • WAYPOINTS_URL은 카카오모빌리티 API의 경로 탐색 엔드포인트를 변수로 담았다.

1-2. API 요청 파라미터 구성

const waypointsParam = Array.isArray(waypoints)
  ? waypoints.map((wp) => `${wp.lng},${wp.lat}`).join("|")
  : "";
  • waypoints가 배열이면 "경유지1_LNG,경유지1_LAT|경유지2_LNG,경유지2_LAT" 형식의 문자열로 변환한다.
  • 카카오 API의 경유지 형식이 LNG,LAT 순서이므로 변환이 필요하다.

1-3. API 호출하기

const response = await axios.get<RouteResponse>(WAYPOINTS_URL, {
  headers: {
    Authorization: `KakaoAK ${KAKAO_REST_API_KEY}`
  },
  params: {
    origin: `${start.lng}, ${start.lat}`,
    destination: `${destination.lng}, ${destination.lat}`,
    waypoints: waypointsParam,
    priority
  }
});

API 요청하기 위해 axios 를 사용했다.

  • 요청 헤더
    • Authorization: KakaoAK {REST_API_KEY} → 카카오 API 사용을 위해 필수로 적어야한다.
  • 요청 파라미터
    • origin: 출발지 (LNG, LAT)
    • destination: 목적지 (LNG, LAT)
    • waypoints: 경유지 (LNG, LAT | LNG, LAT ...)
    • priority: "TIME" 또는 "DISTANCE" (사용자가 select option을 선택할 때 동적으로 바뀌게 설정함)
  • 실제 API응답 예시

성공적으로 불러온 데이터를 보면 이렇게 구성되어있다. 이제 이를 활용해서 기능 구현을 해보려고 한다.


1-4. 데이터 가공 - 경로 탐색 결과 변환하기

if (!response.data.routes.length) {
  console.error("❌ 경로 탐색 실패: 경로가 없습니다.");
  return null;
}

응답 데이터의 routes 값이 비어있는 경우, 경로가 없다는 뜻이므로 null 값을 반환하도록 한다.

✅ sections 배열 값 확인

summary 배열 값 확인

const { summary, sections } = response.data.routes[0];
const { duration, distance } = summary;
  • summary 객체

    • duration: 예상 소요 시간 (초 단위)
    • distance: 총 이동 거리 (미터 단위)
  • sections 객체

    • 세부 경로 데이터를 포함하는 배열


1-5. 카카오 지도 좌표 변환

const path = sections.flatMap((section) =>
  section.roads.flatMap((road) => {
    const coords: kakao.maps.LatLng[] = [];
    for (let i = 0; i < road.vertexes.length; i += 2) {
      coords.push(new window.kakao.maps.LatLng(road.vertexes[i + 1], road.vertexes[i]));
    }
    return coords;
  })
);

vertexes 배열은 [lng1, lat1, lng2, lat2, lng3, lat3, ...] 형태로 제공되기 때문에 카카오 지도 객체와 함께 사용하려면 이 순서를 바꿔주어야한다. (성가심 레전드 🤬)

  • i를 2씩 증가시키면서 lat, lng 좌표를 생성
  • 카카오 지도 객체(new window.kakao.maps.LatLng)로 변환
  • 이 좌표들을 경로(path)로 저장

→ 이 경로 값들을 이어서 polyline 을 그려주는 형태이다!

✅ 변환 결과 예시

[
  new kakao.maps.LatLng(37.5665, 126.9780),
  new kakao.maps.LatLng(37.5670, 126.9775),
  new kakao.maps.LatLng(37.5705, 126.9770)
]

✅ 최종 반환값

return { summary, path, duration, distance };

summary: 출발지, 목적지, 경유지 정보 포함
path: 최적 경로 좌표 배열
duration: 예상 소요 시간 ()
distance: 총 이동 거리 (미터)

2. RoutePath 컴포넌트

RoutePath 컴포넌트는 부모 컴포넌트에서 지도 객체와 좌표 정보를 받아 사용하고 있으며, 카카오 모빌리티 API를 활용해 지도에 경로를 그리는 역할을 담당한다.

경로를 Polyline(선)으로 표시하고, 예상 소요 시간과 거리를 화면에 출력하는 기능을 포함하고 있다.

RoutePath의 역할

  • fetchRoute를 호출하여 출발지-경유지-목적지의 경로 데이터를 가져옴
  • Polyline(경로선)을 생성하여 지도 위에 표시 (카카오 지도 API 사용)
  • 예상 소요 시간 및 이동 거리를 UI에 표시
  • 지도 변경(위치/경로 수정) 시 기존 경로를 제거 후 새 경로 추가

2-1. Props / State / Ref 정리

✅ Props 정의

interface RoutePathProps {
  map: kakao.maps.Map | null; // 지도 객체
  start: { lat: number; lng: number }; // 출발지 좌표
  waypoints: { lat: number; lng: number }[]; // 경유지 리스트 배열 형태 저장
  destination: { lat: number; lng: number }; // 목적지 좌표
  priority: "TIME" | "DISTANCE"; // 경로 탐색 기준 ("TIME": 최단 시간, "DISTANCE": 최단 거리)
}

✅ State / Ref 정의

const [, setRoutePath] = useState<kakao.maps.LatLng[]>([]); // routePath 미사용으로 제거
const [routeInfo, setRouteInfo] = useState<{ duration: number; distance: number } | null>(null);
const polylineRef = useRef<kakao.maps.Polyline | null>(null);
  • routeInfo: 예상 소요 시간 & 이동 거리 저장
  • polylineRef: 현재 지도에 표시된 경로선(Polyline) 객체 저장
  • setRoutePath: 경로 좌표 저장용, (현재 routePath 변수는 사용되지 않으므로 제거함)

→ PolyLine(경로선)의 참조값을 useRef로 저장해, 기존 경로를 제거하는데 활용한다.


2-2. 경로 탐색 및 지도 표시 (useEffect)

useEffect(() => {
  if (!map || !start || !destination || waypoints.length === 0) return;
  • 지도가 로드되지 않았거나, 출발지-목적지가 없거나, 경유지가 없으면 API 호출하지 않음
fetchRoute(start, waypoints, destination, priority).then((response) => {
  if (!response) return;
  setRouteInfo({ duration: response.duration, distance: response.distance });
  • fetchRoute 함수 호출 : 최적 경로, 예상 거리 및 소요 시간 데이터를 가져옴
  • 경로 정보를 routeInfo에 저장하여 UI에 반영
// 기존 Polyline이 있으면 지도에서 제거
if (polylineRef.current) {
  polylineRef.current.setMap(null);
}
  • 이 부분 추가하기 전에 살짝 오류가 있었다. 새로운 경로를 추가 하기 전에 기존 경로를 삭제하는 부분이 중요하다.
// 새로운 Polyline 생성 및 추가
const polyline = new window.kakao.maps.Polyline({
  path: response.path, // 가져온 경로 좌표
  strokeWeight: 5, // 선 두께
  strokeColor: "#6A31F6", // 선 색상 (보라색)
  strokeOpacity: 0.8, // 투명도
  strokeStyle: "solid" // 선 스타일
});

polyline.setMap(map); // 지도에 Polyline 추가
polylineRef.current = polyline; // Polyline 객체 저장
  • Polyline을 생성하여 경로를 지도에 표시 (카카오 지도 선 표시하기 API 사용)
  • path 값으로 fetchRoute에서 가져온 최적 경로를 사용
  • polylineRef.current = polyline; : Polyline 객체를 저장하여 이후 삭제 가능

✅ 결과화면


2-3. 클린업

return () => {
  if (polylineRef.current) {
    polylineRef.current.setMap(null);
    polylineRef.current = null;
  }
};
  • 컴포넌트가 언마운트되거나, 경로 정보가 변경되면 기존 Polyline 제거
  • 이전 경로가 남아 있지 않도록 관리하는 역할

2-4. 예상 소요 시간 및 이동 거리 표시

{routeInfo && (
  <div className="absolute left-52 top-4 z-50 rounded bg-white p-2 text-sm font-semibold shadow dark:text-black">
    ⏳ 예상 소요 시간: {Math.round(routeInfo.duration / 60)}<br />
    📏 예상 이동 거리: {(routeInfo.distance / 1000).toFixed(1)}km
  </div>
)}
  • 소요 시간은 분(Math.round(duration / 60)) 단위로 변환
  • 이동 거리는 km(distance / 1000) 단위로 변환하여 소수점 1자리 표시
  • absolute 클래스로 지도의 특정 위치(왼쪽 상단)에 정보 표시


3. Map 컴포넌트

⭐️ 경로 탐색과 관련된 로직만 정리

Map 컴포넌트는 카카오 지도 API를 활용하여 지도를 렌더링하고, 경로 탐색 기능을 제공하는 핵심 컴포넌트이다.

  • 지도 초기화 및 로드하기
  • 사용자 위치 기반 경로 탐색 기능 (RouthPath 와 연결)
  • 최단 시간 vs 최단 거리 선택 기능
  • 경로 탐색 버튼으로 polyline(경로선) 표시 / 숨김 처리

RouthPath 와 연결관계

  1. RoutePath : 경로 데이터를 가져와 지도에 Polyline을 그리는 역할
  2. Map 컴포넌트에서는 RoutePath를 언제, 어떤 데이터로 호출할지 결정한다.
  3. fetchRouteRoutePath 내부에서 API를 호출하여 경로 데이터를 가져오며, Map 컴포넌트는 이를 제어하는 역할을 한다.

RouthPath 실행

// 경로 탐색 버튼 클릭 시 동작 (경로 표시/숨김)
const handleRouteToggle = () => {
  if (isRouteVisible) {
    setIsRouteVisible(false);
    setRouteData(null);
  } else {
    setRouteData({
      start: { lat: location.latitude, lng: location.longitude }, // 출발지 = 현재 위치
      waypoints: [
        { lat: 37.5154133, lng: 126.9071288 }, 
        { lat: 37.52626250000001, lng: 126.8959528 } 
      ],
      destination: { lat: 37.521638, lng: 126.9049865 } // 목적지
    });
    setIsRouteVisible(true);
  }
};
  • handleRouteToggle() 에서 setIsRouteVisible(true)를 설정하면 RoutePath가 표시된다.

    • isRouteVisiblefalse면 경로를 숨김 (setRouteData(null)RoutePath 제거)
  • 출발지는 사용자의 현재 위치를 불러오는 함수를 사용

  • 경유지, 목적지는 위도, 경도 값을 하드코딩으로 적용했다. (PoC 기능 구현 중)

  • priority 값 ("TIME" 또는 "DISTANCE")를 설정 가능

{map && isRouteVisible && routeData && (
  <RoutePath
    map={map}
    start={routeData.start}
    waypoints={routeData.waypoints}
    destination={routeData.destination}
    priority={priority}
  />
)}
  • isRouteVisibletrue이고 routeData가 존재하면 RoutePath 컴포넌트를 렌더링한다.
  • RoutePath 에서 fetchRoute를 호출하여 경로 데이터를 가져오고, Polyline을 지도에 그린다.

경로탐색 기준 설정

RoutePath 컴포넌트에 priority 값을 전달하여 API 요청 시 적용된다.

{/* 최단 시간 vs 최단 거리 선택 */}
<div className="absolute left-4 top-4 z-50 rounded bg-white p-2 shadow dark:text-black">
  <label className="text-sm font-semibold">경로 기준:</label>
  <select
    className="ml-2 rounded border p-1"
    value={priority}
    onChange={(e) => setPriority(e.target.value as "TIME" | "DISTANCE")}
  >
    <option value="TIME">최단 시간</option>
    <option value="DISTANCE">최단 거리</option>
  </select>
</div>
  • 선택한 값(priority)이 RoutePath에 props로 전달
  • fetchRoute 호출 시 "TIME" or "DISTANCE"로 요청하여 동적으로 바뀌도록 함


참고 자료 📚

profile
왕쪼랩 탈출 목표자의 코딩 공부기록

0개의 댓글