Skip to content

Panorama 서브모듈

Panorama 서브모듈은 거리뷰(Street View)와 항공뷰(Aerial View)를 제공합니다.

라이브 데모

개요

Panorama 모듈은 다음 기능을 제공합니다:

  • 거리뷰: 360도 파노라마 이미지로 거리를 탐색
  • 항공뷰: 항공에서 촬영한 파노라마 이미지
  • 지도 연동: 지도와 파노라마 간 위치 동기화
  • 마커 지원: 파노라마 내부에 Marker 컴포넌트 사용 가능

서브모듈 로드

NaverMapProvidersubmodules prop을 통해 로드합니다:

tsx
import { NaverMapProvider } from "react-naver-maps-kit";

function App() {
  return (
    <NaverMapProvider ncpKeyId={import.meta.env.VITE_NCP_KEY_ID} submodules={["panorama"]}>
      {/* 앱 컴포넌트 */}
    </NaverMapProvider>
  );
}

컴포넌트

Panorama

파노라마 뷰어 컴포넌트입니다.

tsx
import { Panorama } from "react-naver-maps-kit";

function PanoramaView() {
  return (
    <Panorama
      defaultPosition={{ lat: 37.3595704, lng: 127.105399 }}
      defaultPov={{ pan: 0, tilt: 0, fov: 100 }}
      flightSpot={true}
      aroundControl={true}
      style={{ width: "100%", height: "500px" }}
      onInit={() => console.log("Panorama initialized")}
      onPanoChanged={() => console.log("Panorama changed")}
      onPovChanged={() => console.log("POV changed")}
    />
  );
}

타입 정의

ts
interface PanoramaOptionProps {
  size?: naver.maps.Size | naver.maps.SizeLiteral;
  panoId?: string;
  position?: { lat: number; lng: number } | naver.maps.LatLng;
  defaultPosition?: { lat: number; lng: number } | naver.maps.LatLng;
  pov?: { pan?: number; tilt?: number; fov?: number };
  defaultPov?: { pan?: number; tilt?: number; fov?: number };
  visible?: boolean;
  minScale?: number;
  maxScale?: number;
  minZoom?: number;
  maxZoom?: number;
  flightSpot?: boolean;
  logoControl?: boolean;
  logoControlOptions?: naver.maps.LogoControlOptions;
  zoomControl?: boolean;
  zoomControlOptions?: naver.maps.ZoomControlOptions;
  aroundControl?: boolean;
  aroundControlOptions?: naver.maps.AroundControlOptions;
}

interface PanoramaLifecycleProps {
  children?: React.ReactNode;
  onPanoramaReady?: (panorama: naver.maps.Panorama) => void;
  onPanoramaDestroy?: () => void;
  onPanoramaError?: (error: Error) => void;
}

interface PanoramaEventProps {
  onInit?: () => void;
  onPanoChanged?: () => void;
  onPanoStatus?: (status: string) => void;
  onPovChanged?: () => void;
}

type PanoramaProps = PanoramaOptionProps &
  PanoramaLifecycleProps &
  PanoramaEventProps & {
    style?: React.CSSProperties;
    className?: string;
  };

interface PanoramaRef {
  getInstance: () => naver.maps.Panorama | null;
  getElement: () => HTMLElement | undefined;
  getLocation: () => naver.maps.PanoramaLocation | undefined;
  getMaxScale: () => number | undefined;
  getMaxZoom: () => number | undefined;
  getMinScale: () => number | undefined;
  getMinZoom: () => number | undefined;
  getOptions: (key?: string) => unknown;
  getPanoId: () => string | undefined;
  getPosition: () => naver.maps.LatLng | undefined;
  getPov: () => { pan?: number; tilt?: number; fov?: number } | undefined;
  getProjection: () => naver.maps.PanoramaProjection | undefined;
  getScale: () => number | undefined;
  getSize: () => naver.maps.Size | undefined;
  getVisible: () => boolean | undefined;
  getZoom: () => number | undefined;
  setOptions: (options: naver.maps.PanoramaOptions) => void;
  setPanoId: (panoId: string) => void;
  setPanoIdWithPov: (panoId: string, pov: { pan?: number; tilt?: number; fov?: number }) => void;
  setPosition: (position: naver.maps.LatLng) => void;
  setPov: (pov: { pan?: number; tilt?: number; fov?: number }) => void;
  setScale: (scale: number) => void;
  setSize: (size: naver.maps.Size) => void;
  setVisible: (visible: boolean) => void;
  setZoom: (zoom: number) => void;
  zoomIn: () => void;
  zoomOut: () => void;
}

Props

Prop타입설명
position{ lat: number; lng: number }파노라마 위치 (controlled)
defaultPosition{ lat: number; lng: number }초기 파노라마 위치 (uncontrolled)
panoIdstring파노라마 ID
pov{ pan, tilt, fov }시점 (controlled)
defaultPov{ pan, tilt, fov }초기 시점 (uncontrolled)
visibleboolean표시 여부
sizenaver.maps.Size파노라마 크기
minScalenumber최소 스케일
maxScalenumber최대 스케일
minZoomnumber최소 줌 레벨
maxZoomnumber최대 줌 레벨
flightSpotboolean항공뷰 전환 버튼 표시
aroundControlboolean주변 보기 컨트롤 표시
aroundControlOptionsnaver.maps.AroundControlOptions주변 보기 컨트롤 옵션
zoomControlboolean줌 컨트롤 표시
zoomControlOptionsnaver.maps.ZoomControlOptions줌 컨트롤 옵션
logoControlboolean로고 컨트롤 표시
logoControlOptionsnaver.maps.LogoControlOptions로고 컨트롤 옵션
styleCSSProperties컨테이너 스타일
classNamestring컨테이너 클래스명
childrenReactNode내부 마커 등 자식 컴포넌트
onPanoramaReady(panorama) => void인스턴스 생성 완료 콜백
onPanoramaDestroy() => void인스턴스 정리 완료 콜백
onPanoramaError(error: Error) => void생성 실패 콜백
onInit() => void초기화 완료 이벤트
onPanoChanged() => void파노라마 변경 이벤트
onPovChanged() => void시점 변경 이벤트
onPanoStatus(status: string) => void파노라마 상태 변경 이벤트

Controlled vs Uncontrolled

positionpov는 controlled/uncontrolled 패턴을 지원합니다:

tsx
// Uncontrolled: 초기값만 설정, 이후 내부 상태로 관리
<Panorama
  defaultPosition={{ lat: 37.5665, lng: 126.978 }}
  defaultPov={{ pan: 0, tilt: 0, fov: 100 }}
/>;

// Controlled: React 상태와 동기화
const [position, setPosition] = useState({ lat: 37.5665, lng: 126.978 });
const [pov, setPov] = useState({ pan: 0, tilt: 0, fov: 100 });

<Panorama
  position={position}
  pov={pov}
  onPovChanged={() => {
    // pov 상태 업데이트
  }}
/>;

Ref Methods

tsx
import { useRef } from "react";
import { Panorama, type PanoramaRef } from "react-naver-maps-kit";

function PanoramaWithRef() {
  const panoramaRef = useRef<PanoramaRef>(null);

  return (
    <>
      <Panorama ref={panoramaRef} defaultPosition={{ lat: 37.5665, lng: 126.978 }} />
      <button
        onClick={() => {
          const pos = panoramaRef.current?.getPosition();
          console.log(pos?.lat(), pos?.lng());
        }}
      >
        Get Position
      </button>
    </>
  );
}
메서드반환 타입설명
getInstance()naver.maps.Panorama파노라마 인스턴스
getElement()HTMLElement컨테이너 DOM 요소
getLocation()naver.maps.PanoramaLocation현재 위치 정보
getPosition()naver.maps.LatLng현재 위치
setPosition()void위치 설정
getPov(){ pan, tilt, fov }현재 시점
setPov()void시점 설정
getPanoId()string현재 파노라마 ID
setPanoId()void파노라마 ID 설정
setPanoIdWithPov()voidID와 시점 동시 설정
getZoom()number현재 줌 레벨
setZoom()void줌 레벨 설정
zoomIn()void줌 인
zoomOut()void줌 아웃
getScale()number현재 스케일
setScale()void스케일 설정
getMinScale()number최소 스케일
getMaxScale()number최대 스케일
getMinZoom()number최소 줌 레벨
getMaxZoom()number최대 줌 레벨
getSize()naver.maps.Size파노라마 크기
setSize()void크기 설정
getVisible()boolean표시 여부
setVisible()void표시 여부 설정
getOptions()unknown옵션 조회
setOptions()void옵션 설정
getProjection()naver.maps.PanoramaProjection투영 객체

FlightSpot

지도 위에 항공뷰 위치를 표시하는 컴포넌트입니다.

tsx
import { NaverMap, FlightSpot } from "react-naver-maps-kit";

function MapWithFlightSpot() {
  return (
    <NaverMap defaultCenter={{ lat: 37.5665, lng: 126.978 }} defaultZoom={15}>
      <FlightSpot
        onPoiClicked={(panoId) => {
          console.log("FlightSpot clicked:", panoId);
        }}
      />
    </NaverMap>
  );
}

파노라마 내부 마커

Panorama 컴포넌트 내부에서 Marker를 사용할 수 있습니다:

tsx
import { Panorama, Marker } from "react-naver-maps-kit";

function PanoramaWithMarkers() {
  return (
    <Panorama defaultPosition={{ lat: 37.3595704, lng: 127.105399 }}>
      <Marker position={{ lat: 37.3595704, lng: 127.105399 }}>
        <div
          style={{
            background: "#FF5722",
            color: "white",
            padding: "4px 10px",
            borderRadius: 16
          }}
        >
          네이버 본사
        </div>
      </Marker>
    </Panorama>
  );
}

활용 예제

지도와 파노라마 연동 (Controlled)

tsx
import { useState } from "react";
import { NaverMap, Marker, Panorama, FlightSpot } from "react-naver-maps-kit";

function MapWithPanorama() {
  const [position, setPosition] = useState({ lat: 37.3595704, lng: 127.105399 });
  const [pov, setPov] = useState({ pan: 0, tilt: 0, fov: 100 });

  return (
    <div style={{ display: "flex", gap: "16px" }}>
      <NaverMap center={position} zoom={15} style={{ width: "50%", height: "400px" }}>
        <Marker
          position={position}
          draggable={true}
          onDragEnd={(e) => {
            setPosition({ lat: e.coord.y, lng: e.coord.x });
          }}
        />
        <FlightSpot />
      </NaverMap>
      <Panorama
        position={position}
        pov={pov}
        onPovChanged={() => {
          // POV 변경 시 필요한 로직
        }}
        style={{ width: "50%", height: "400px" }}
      />
    </div>
  );
}

FlightSpot으로 항공뷰 진입

tsx
import { useState } from "react";
import { NaverMap, FlightSpot, Panorama } from "react-naver-maps-kit";

function MapWithFlightSpot() {
  const [showPanorama, setShowPanorama] = useState(false);
  const [panoId, setPanoId] = useState<string | null>(null);

  return (
    <div style={{ display: "flex", gap: "16px" }}>
      <NaverMap
        defaultCenter={{ lat: 37.5665, lng: 126.978 }}
        defaultZoom={15}
        style={{ width: "50%", height: "400px" }}
      >
        <FlightSpot
          onPoiClicked={(id) => {
            setPanoId(id);
            setShowPanorama(true);
          }}
        />
      </NaverMap>

      {showPanorama && panoId && (
        <Panorama panoId={panoId} style={{ width: "50%", height: "400px" }} />
      )}
    </div>
  );
}

POV (시점) 제어

tsx
import { useState } from "react";
import { Panorama } from "react-naver-maps-kit";

function PanoramaControls() {
  const [pov, setPov] = useState({ pan: 0, tilt: 0, fov: 100 });

  const rotateLeft = () => {
    setPov((prev) => ({ ...prev, pan: prev.pan - 45 }));
  };

  const rotateRight = () => {
    setPov((prev) => ({ ...prev, pan: prev.pan + 45 }));
  };

  return (
    <div>
      <Panorama
        defaultPosition={{ lat: 37.5665, lng: 126.978 }}
        pov={pov}
        style={{ width: "100%", height: "400px" }}
      />
      <div style={{ marginTop: 8 }}>
        <button onClick={rotateLeft}>← 왼쪽 회전</button>
        <button onClick={rotateRight}>오른쪽 회전 →</button>
      </div>
    </div>
  );
}

이벤트

이벤트설명
init초기화 완료
pano_changed파노라마 ID 변경
pov_changed시점 변경
pano_status파노라마 상태 변경

참고

Released under the MIT License.