카카오지도 API - 현 위치 재검색, 마커, 클러스터러

LEEJAEJUN·2024년 2월 7일

yanabada

목록 보기
2/3
post-thumbnail

구현 사항

내 위치로 가기 & 내 위치 마커

📌 `navigator.geolocation.getCurrentPosition(success, error, [options])`

현재 위치를 찾기 위해서 자바스크립트의 Geolocation API를 사용하면 된다. 일반적으로 아래와 같이 사용할 수 있다. latitude는 위도, longitude는 경도다. 아직도 조금 헷갈리는 부분이 위도 경도가 x, y와 대치되는 개념이 아니라는 것이다. 지구는 둥그니까..(?)

한 가지 알게 된 사실은 사파리에서 현재 위치 찾기는 누르자마자 작동하는 반면, 크롬 브라우저에서 현재 위치 찾기는 가끔 5초 정도 딜레이가 있다는 점이다. 다행히 참고할 만한 글이 있었다. https://sypear.tistory.com/75

navigator.geolocation.getCurrentPosition((position) => {
  doSomething(position.coords.latitude, position.coords.longitude);
});

숙소 리스트 마커 & 클러스터러

📌 지도에 핀을 어떻게 찍을지, 커스텀은 어떻게 할지 개념을 이해하는 게 중요했다.

커스텀 오버레이 핀

만들어진 지도에 커스텀 오버레이를 추가하면 된다.

마커 클러스터러

만들어진 지도에 마커 클러스터러를 추가하면 된다. 그다음 클러스터러에 마커를 추가한다.

🚨 Props를 State에 미러링하는 문제

📌 마커를 클릭했을 때 선택된 상품을 변경하기 위해 **setter와 state**를 함께 Markers 컴포넌트의 props로 전달했다. 하지만 KakaoMap의 props로 받은 `products`를 `selectedProductId`의 초기값으로 전달했더니 마커를 클릭해도 선택이 되지 않는 문제가 발생했다.

기억을 더듬어 보니 내가 공식 문서에 있었던 문제를 그대로 복습하고 있었다. Props를 초기값으로 사용하면 처음 렌더링 시에만 초기화되고 다음부터는 업데이트 되지 않는다는 것…! 그래서 부득이 useEffect로 products가 바뀌면 selectedProductId를 업데이트 하도록 했다. 더 좋은 방법이 있을 것 같은데, 아직은 잘 모르겠다.

📌 다만 의도적으로 미러링을 해야할 때가 있었다. 현 위치에서 재검색 시 products가 바뀌면서 지도의 중앙이 의도치 않게 바뀌는 문제 때문이었는데, 이때는 하단의 `mapCenter state`처럼 products를 props로 초기화하고 다음부터 업데이트 하지 않았다. 이렇게 해서 products가 바뀌더라도 의도치 않게 지도의 중앙이 바뀌는 문제를 막을 수 있었다.
  • KakaoMap.tsx
    const KakaoMap = ({ products }: MapProps) => {
    	const [selectedProductId, setSelectedProductId] = useState(
    	  products.length > 0 ? products[0].id : 0
    	);
    
    	const [mapCenter, setMapCenter] = useState<MapCenter>({
        lat: products.length > 0 ? products[0].latitude : 33.450701,
        lng: products.length > 0 ? products[0].longitude : 126.570667
      });
    	
    	useEffect(() => {
    	  if (products.length > 0) {
    	    setSelectedProductId(products[0].id);
    	  }
    	}, [products]);
    	
    	return (
    		<Map
          center={mapCenter}
          ref={mapRef}
        >
    			<ProductsMarkers
    			  products={products}
    			  selectedProductId={selectedProductId}
    			  setSelectedProductId={setSelectedProductId}
    			/>
    		{/* ... */}
    	)

지도 이동 시 숙소 재검색 버튼 활성화

📌 처음엔 `center_changed` 이벤트를 사용했는데, 지도 이동을 시작하자마자 재검색 버튼이 뜨는 게 조금 어색해서 `dragend` 이벤트로 변경했다. 또 지도를 이동할 때 SW와 NE의 위도 경도 값을 구하도록 훅을 만들어 두었는데 `center_changed`의 경우 옮기는 동안 계속 좌표를 구하게 되면서 불필요한 계산이 많아지는 문제가 있었다.

지도 이동이 끝나면 isMapMovedtrue로 바뀌면서 현 위치에서 재검색 버튼이 활성화되고, position 상태에 위도, 경도 값을 저장한다. 재검색 버튼을 누르면 position을 url에 저장하고, url이 바뀌면서 react query 훅이 실행된다.

  • useResearch hook
    import { useEffect, useState } from "react";
    import { useMap } from "react-kakao-maps-sdk";
    
    interface Position {
      smallX: number;
      smallY: number;
      bigX: number;
      bigY: number;
    }
    
    const useResearch = () => {
      const [isMapMoved, setMapMoved] = useState(false);
      const [position, setPosition] = useState<Position>();
      const map = useMap();
    
      useEffect(() => {
        if (!map) return;
    
        const centerChangedListener = (map: kakao.maps.Map) => {
          setMapMoved(true);
    
          const bounds = map.getBounds();
          const swLatLng = bounds.getSouthWest();
          const neLatLng = bounds.getNorthEast();
    
          setPosition({
            smallX: swLatLng.getLat(),
            smallY: swLatLng.getLng(),
            bigX: neLatLng.getLat(),
            bigY: neLatLng.getLng()
          });
        };
    
        kakao.maps.event.addListener(map, "dragend", () => centerChangedListener(map));
    
        return () => {
          if (map) {
            kakao.maps.event.removeListener(map, "dragend", () => centerChangedListener(map));
          }
        };
      }, [map]);
    
      return { position, isMapMoved, setMapMoved };
    };
    
    export default useResearch;
  • Research.tsx
    import useResearch from "@pages/products/hooks/useResearch";
    import * as S from "./styles";
    import { useSearchParams } from "react-router-dom";
    
    const Research = () => {
      const { position, isMapMoved, setMapMoved } = useResearch();
      const [searchParams, setSearchParams] = useSearchParams();
    
      const handleResearch = () => {
        searchParams.set("swx", position.smallX.toString());
        searchParams.set("swy", position.smallY.toString());
        searchParams.set("nex", position.bigX.toString());
        searchParams.set("ney", position.bigY.toString());
        setSearchParams(searchParams);
        setMapMoved(false);
      };
    
      return (
        isMapMoved && (
          <S.ResearchButton onClick={handleResearch}>현재 지도에서 숙소 재검색</S.ResearchButton>
        )
      );
    };
    
    export default Research;
profile
always be fresh

0개의 댓글