Front-End/React

[React] 카카오맵(1) 지도 내장 검색 기능 사용해 리스트 출력하기

Olivia Kim 2023. 6. 1. 23:42
반응형

 

 

서울시 내 위스키바 리스트를 출력하기 위해 방법을 고민하던 중, 카카오맵 API에서 위와 같이 키워드로 장소를 검색하고 목록으로 표출하는 기능을 제공해 준다는 것을 알게 되었다. 데이터를 크롤링할 것인지 vs 카카오 내장 검색 기능을 사용할 것인지 회의를 진행했고 지도 API에서 어차피 리스트를 제공해 준다면 해당 내장 기능을 최대한 활용해 보기로 결론을 내렸다.

 

 

 


구현하고자 하는 로직

1. 페이지가 마운트될 때 사용자 현재 위치 정보 동의
  1-1. 사용자가 허용했을 경우 해당 위도/경도 설정
  1-2. 사용자가 거부했을 경우 지도 설정 기본 값인 을지로 3가의 위도/경도 설정
2. props로 내려받은 위도/경도값을 주소로 변환해 'X시 Y동 칵테일바'라는 키워드 세팅
3. 해당 키워드를 이용해 지도 API 내부 검색 기능 이용
4. 검색된 결과 리스트를 지도에 마커로 세팅

 

 

 


구현한 코드

StoreList.jsx 전체 코드 한 번에 보기

더보기
import React, { useState, useEffect } from 'react';
import { styled } from 'styled-components';
import { MdOutlineGpsFixed } from 'react-icons/md';
import { Layout, SearchInput, Button, DetailList } from '../components';
import KakaoMap from './KakaoMap';

const StoreList = () => {
  const [coords, setCoords] = useState({ lat: 0, lon: 0 });
  const [currentLocationToggle, setCurrentLocationToggle] = useState(false);
  const [toggleId, setToggleId] = useState(0);
  const [nearbyToggle, setNearbyToggle] = useState(false);

  // * 사용자 위치정보 허용했을 경우 해당 위도/경도 설정
  const approve = (position) => setCoords({ lat: position.coords.latitude, lon: position.coords.longitude });

  // * 사용자 위치정보 거절하거나 오류일 경우 을지로 3가 위도/경도 설정
  const reject = () => setCoords({ lat: 37.566498652285, lon: 126.99209745028 });

  // * 현재 위치 버튼 클릭 시 currentLocationToggle true/false 값 변경
  const onCurrentLocationClickHandler = () => setCurrentLocationToggle(!currentLocationToggle);

  // * 페이지가 처음 마운트될 때 위치 정보 동의
  useEffect(() => {
    navigator.geolocation.getCurrentPosition(approve, reject);
  }, []);

  // * currentLocationToggle true/false값에 따라 현재 위치 GPS 추적 여부 변경
  useEffect(() => {
    if (currentLocationToggle) {
      // 장치 위치가 변경될 때마다 위치 추적
      const watchId = navigator.geolocation.watchPosition(approve, reject);
      setToggleId(watchId);
    } else {
      // watchPosition 실행 중지
      navigator.geolocation.clearWatch(toggleId);
    }
  }, [currentLocationToggle]);

  const isShowListHandler = () => setNearbyToggle(!nearbyToggle);

  return (
    <Layout>
      <ListSection>
        <SearchInput searchtype={'before'} placeholder={'위스키 바를 검색할 지역을 입력해보세요!'} />
        <KakaoMap coords={coords} />
        <Button size={'small'} color={'white'} onClick={isShowListHandler}>
          목록 보기
        </Button>
        <button type="button" onClick={onCurrentLocationClickHandler}>
          <MdOutlineGpsFixed />
        </button>
      </ListSection>

      {nearbyToggle && (
        <>
          <BackgroundDiv onClick={isShowListHandler} position="Layout" />
          <NearbyListDiv>
            <p>주변 위스키바</p>
            <div>
              <DetailList type={'store'} />
            </div>
            <div>
              <Button onClick={isShowListHandler}>닫기</Button>
            </div>
          </NearbyListDiv>
        </>
      )}
    </Layout>
  );
};

export default StoreList;

 

 

KakaoMap.jsx 전체 코드 한번에 보기

더보기
import React, { useState, useEffect } from 'react';
import { Map, MapMarker } from 'react-kakao-maps-sdk';
import { styled } from 'styled-components';

// window.kakao 객체를 가져옴
const { kakao } = window;

const KakaoMap = ({ coords }) => {
  const [info, setInfo] = useState();
  const [markers, setMarkers] = useState([]);
  const [map, setMap] = useState();
  const [keyword, setKeyword] = useState('');

  // * coords 값에 따라 keyword에 넣을 'X시 + Y동 + 칵테일바' 값 set (단, lat, lon이 0이 아닐 때)
  useEffect(() => {
    if (coords.lat !== 0 && coords.lon !== 0) {
      // 주소-좌표 변환 객체를 생성
      const geocoder = new kakao.maps.services.Geocoder();
      // 좌표 값에 해당하는 구 주소와 도로명 주소 정보를 요청
      // - x : x좌표 (longitude) / y : y좌표 (latitude)
      // - callback : 검색 결과를 받을 콜백함수
      geocoder.coord2Address(coords.lon, coords.lat, (address) => {
        const getAddressName = address[0].address;
				// keyword값 예시 : 부천시 중동 칵테일바
        setKeyword(`${getAddressName.region_2depth_name} ${getAddressName.region_3depth_name} 칵테일바`);
      });
    }
  }, [coords]);

  // * keyword값에 따라 지도 API 내부 검색, 검색 결과를 지도에 마커로 set
  useEffect(() => {
    // keyword가 초기의 빈값일 경우 API 요청을 수행하지 않음
    if (!keyword || keyword.trim() === '' || !map) return;

    // 장소 검색 서비스 객체 생성
    const ps = new kakao.maps.services.Places();

    // 입력한 키워드로 검색
    // - keyword: 검색할 키워드
    // - callback: 검색 결과를 받을 콜백함수
    ps.keywordSearch(keyword, (data, status) => {
      if (status === kakao.maps.services.Status.OK) {
        // 검색된 장소 위치를 기준으로 지도 범위를 재설정하기위해 LatLngBounds 객체에 좌표 추가
        const bounds = new kakao.maps.LatLngBounds();
        const addMarkers = [];

        for (let i = 0; i < data.length; i += 1) {
          addMarkers.push({
            position: {
              lat: data[i].y,
              lng: data[i].x,
            },
            content: data[i].place_name,
          });
          // 인수로 주어진 좌표를 포함하도록 영역 정보를 확장
          bounds.extend(new kakao.maps.LatLng(data[i].y, data[i].x));
        }
        setMarkers(addMarkers);

        // 검색된 장소 위치를 기준으로 지도 범위를 재설정
        map.setBounds(bounds);
      }
    });
  }, [map, keyword]);

  // JSX로 지도와 마커를 렌더링
  return (
    <MapSection>
      {/* 로드뷰를 표시할 Container */}
      <Map
        // 중심으로 설정할 위치
        center={{
          lat: coords.lat,
          lng: coords.lon,
        }}
        style={{
          width: '100%',
          height: '94%',
        }}
        level={3}
        onCreate={setMap}
      >
        {markers.map((marker) => (
          <MapMarker
            key={`marker-${marker.content}-${marker.position.lat},${marker.position.lng}`}
            // 표시 위치
            position={marker.position}
            onClick={() => setInfo(marker)}
          >
            {info && info.content === marker.content && <div style={{ color: '#000' }}>{marker.content}</div>}
          </MapMarker>
        ))}
      </Map>
    </MapSection>
  );
};

export default KakaoMap;

const MapSection = styled.section`
  width: 360px;
  height: 100vh;
  margin-left: -17px;
`;

 

 

 


해당 기능의 한계점

 

카카오맵 API 내장 검색 기능으로 가져올 수 있는 데이터는 위와 같이 '업장명, 업장 주소, 업장 전화번호' 까지였다.

 

 

 

하지만 추가로 필요한 데이터들 (ID값, 잔여 좌석, 대표 이미지 등) 이 있었기 때문에 지도 API 내부 검색 기능으로는 구현에 한계가 있었다. 해당 상황을 공유한 후 팀원들과 다시 논의해 위스키바 데이터를 아예 크롤링해 DB에 저장한 후 해당 데이터를 지도에 불러오는 방식으로 로직을 다시 구성하기로 했다.

 

 

 


[참고 자료]

https://apis.map.kakao.com/web/sample/keywordList/

 

 

 

반응형