준영이의 성장일기(FrontEnd)
Open Api를 활용한 여행 일정 구상하는 프로젝트(1) 본문
교내 동아리에서 진행 중인 Open API를 활용한 프로젝트를 주제로 스터디를 하고 있다. 현재 활용하고 있는 오픈 API로는 Google Map API, 파이어베이스 API, 한국관광공사_국문 관광정보 서비스_GW API 총 3 가지이고 오늘 한 태스크는 현재 위치를 기반으로 Google Map에 관광지에 따라서 마커가 랜더링 되는 기능을 구현하였다.
<개발 과정>
zustand 상태관리 라이브러리를 사용하였고 위치기반 관광지역 정보를 타입스크립트를 적용하여 받아왔다. Open API 문서를 다운 받고 사용할려는 API의 엔드포인트 , 위치기반 관광지역 정보에 해당하는 문서의 챕터를 보면서 request로 어떤걸 보내야하는지 response로는 무엇인지 알아야 했다. 그래야 타입스크립트 적용이 가능하기 때문!
import { create } from 'zustand';
import axios from 'axios';
export interface TourismItem {
title: string;
mapx: number;
mapy: number;
addr1?: string;
addr2?: string;
contentid: number;
contenttypeid: number;
createdtime: string;
dist: number;
firstimage: string;
firstimage2?: string;
cpyrhtDivCd?: string;
mlevel?: number;
modifiedtime: string;
sigungucode?: number;
tel?: string;
}
interface TourismResponse {
response: {
body: {
items: {
item: TourismItem[];
};
totalCount: number;
};
header: {
resultCode: string;
resultMsg: string;
};
};
}
interface TourStore {
tourismData: TourismItem[];
fetchTourismData: (lat: number, lng: number) => Promise<void>;
}
const useTourStore = create<TourStore>((set) => ({
tourismData: [],
fetchTourismData: async (lat: number, lng: number) => {
try {
const encodedServiceKey = import.meta.env.VITE_TOUR_API_KEY;
const serviceKey = decodeURIComponent(encodedServiceKey);
const params = {
serviceKey: serviceKey,
numOfRows: 10,
pageNo: 1,
MobileOS: 'ETC',
MobileApp: 'AppTest',
_type: 'json',
listYN: 'Y',
arrange: 'A',
contentTypeId: 15,
mapX: lng,
mapY: lat,
radius: 5000,
};
const url =
'http://apis.data.go.kr/B551011/KorService1/locationBasedList1';
console.log('Requesting with params:', params);
console.log('Request URL:', url, params);
const response = await axios.get<TourismResponse>(url, { params });
console.log('Full response data:', response.data);
if (
response.data &&
response.data.response &&
response.data.response.body
) {
const data = response.data.response.body;
console.log('Fetched tourism data:', data.items.item);
console.log('Total count of items:', data.totalCount);
set({ tourismData: data.items.item });
} else {
console.error('Unexpected response structure:', response.data);
}
} catch (error) {
console.error('Error fetching tourism data:', error);
}
},
}));
export default useTourStore;
만든 zustand 스토어를 기반으로 GoogleMap에 마커가 랜더링 되도록 구현하였는데 위도, 경도 설정하는 부분에서 숫자로 형 변환을 해줘야 위치를 인식하였다
✅이후 마커를 클릭했을 때 map을 통해 순회하는 요소에 대하여 setSelectedPlace(item)으로 상태 업데이트
✅ 사는 지역인 김포를 기준으로 마커가 여러개 나왔고 마커를 클릭하면 InfoWindow 컴포넌트 랜더링
✅ tourismData는 zustand 스토어에서 만든 useTourStore를 호출했고 그 안에서 tourismData 가져옴(구조분해할당)
✅ fetchTourismData함수에 현재 위도, 경도를 인자로 하여 함수를 호출한다.
{tourismData.map((item, index) => (
<Marker
key={index}
position={{ lat: Number(item.mapy), lng: Number(item.mapx) }}
title={item.title}
onClick={() => setSelectedPlace(item)}
/>
))}
import {
GoogleMap,
useJsApiLoader,
Marker,
InfoWindow,
} from '@react-google-maps/api';
import MapLeftSideBar from './MapLeftSideBar';
import { useState, useEffect } from 'react';
import useTourStore, { TourismItem } from '../../store/store'; // 수정된 부분
const containerStyle = {
width: '100%',
height: '100vh',
};
const initialCenter = {
lat: -3.745,
lng: -38.523,
};
export default function MyGoogleMap() {
const [center, setCenter] = useState(initialCenter);
const { tourismData, fetchTourismData } = useTourStore(); // 수정된 부분
const [selectedPlace, setSelectedPlace] = useState<TourismItem | undefined>(
undefined,
);
const { isLoaded, loadError } = useJsApiLoader({
googleMapsApiKey: import.meta.env.VITE_GOOGLE_MAPS_API_KEY,
});
const updateCenterToCurrentLocation = () => {
if (navigator.geolocation) {
navigator.geolocation.getCurrentPosition(
(position) => {
const newLat = position.coords.latitude;
const newLng = position.coords.longitude;
setCenter({ lat: newLat, lng: newLng });
fetchTourismData(newLat, newLng); // 현재 위치를 기반으로 관광 정보 가져오기
},
(error) => {
console.error('Error getting current location:', error);
},
);
} else {
console.error('Geolocation is not supported by this browser.');
}
};
useEffect(() => {
updateCenterToCurrentLocation();
}, []);
if (loadError) {
return <div>Error loading Google Maps</div>;
}
if (!isLoaded) {
return (
<div className="absolute flex justify-center items-center">
Loading...
</div>
);
}
return (
<div className="relative w-full h-full">
<GoogleMap mapContainerStyle={containerStyle} center={center} zoom={15}>
{tourismData.map((item, index) => (
<Marker
key={index}
position={{ lat: Number(item.mapy), lng: Number(item.mapx) }}
title={item.title}
onClick={() => setSelectedPlace(item)}
/>
))}
{selectedPlace &&
selectedPlace.mapy &&
selectedPlace.mapx && ( // 수정된 부분
<InfoWindow
position={{
lat: Number(selectedPlace.mapy),
lng: Number(selectedPlace.mapx),
}}
onCloseClick={() => setSelectedPlace(undefined)} // 수정된 부분
>
<div>
<h2>{selectedPlace.title}</h2>
</div>
</InfoWindow>
)}
<MapLeftSideBar updateCenter={updateCenterToCurrentLocation} />
</GoogleMap>
</div>
);
}
<구현 화면>
InfoWindow가 랜더링 되기는 하는데 UI 면에서 만족스럽지 않아서 하나의 새로운 컴포넌트를 만들어서 props로 넘기고 랜더링 할 예정이다. 그리고 이번 프로젝트를 해보니 폴더구조, 파일명 정하는게 너무 어려웠다. 아직 폴더구조와 파일명이 정리가 안되서 정리부터 하고 다음 태스크를 해야겠다.
<깃허브>
https://github.com/Jayjunyoung/Our_Trip_Route
GitHub - Jayjunyoung/Our_Trip_Route: Google Map API를 이용한 자신만의 여행 정보 웹 사이트
Google Map API를 이용한 자신만의 여행 정보 웹 사이트. Contribute to Jayjunyoung/Our_Trip_Route development by creating an account on GitHub.
github.com
'프론트엔드 > React.js' 카테고리의 다른 글
UMC 10주차 미션 및 스터디 마무리 (0) | 2024.06.16 |
---|---|
Open Api를 활용한 여행 일정 구상하는 프로젝트(3) (2) | 2024.06.06 |
UMC 영화 페이지에 반응형 디자인 적용하기 (0) | 2024.06.02 |
Open Api를 활용한 여행 일정 구상하는 프로젝트(2) (0) | 2024.05.30 |
UMC TUK 7주차 워크북 미션 (0) | 2024.05.25 |