준영이의 성장일기(FrontEnd)

Open Api를 활용한 여행 일정 구상하는 프로젝트(1) 본문

프론트엔드/React.js

Open Api를 활용한 여행 일정 구상하는 프로젝트(1)

Jay_Jung 2024. 5. 29. 23:32

 

교내 동아리에서 진행 중인 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