leaflet, 반복되는 지도에 marker 표시하기(react)

맵 축소 시 반복되는 지도에 Marker 반영하기

다른 옵션을 설정하지 않는 한 맵을 최대로 축소하면 다음과 같이 지도가 반복되어 나타납니다.

여기서 맵을 오른쪽이나 왼쪽으로 넘겨서 중심을 바꿔도 Marker는 그대로 있습니다.

하지만 맵 렌더링 시 다음과 같이 ‘worldCopyJump’ 옵션을 넣어주면 중심이 이동하더라도 기존의 Marker를 모두 표시할 수 있습니다.

<MapContainer
   center={[35.102, 129.067]}
   zoom={5}
   scrollWheelZoom={true}
   worldCopyJump
>

옵션 하나만으로 맵을 이동하면 Marker도 이동하여 같은 좌표에 표시되는 것을 확인할 수 있습니다.

leaflet 맵 표시 언어 변경

지도 데이터 서버 변경하기

leaflet의 기본 맵은 각 지역마다 현지 나라의 언어로 표기되어 한국 맵은 한국어, 일본 맵은 일본어, 프랑스 맵은 프랑스어로 표기되어 있습니다.

하지만 전체 맵을 영어로 표기하고 싶거나 산맥, 해양 등 용도에 맞게 지도의 이미지를 변경하고 싶을 때는 아래 링크에서 원하는 맵의 유형과 서버를 확인하여 서버만 변경해주면 됩니다.

1. 링크에서 서버 확인

http://leaflet-extras.github.io/leaflet-providers/preview/

링크에 접속하면 다음과 같은 화면입니다.

초록색 네모칸에서 서버 정보, 빨간색 네모칸에서 맵의 종류 샘플을 볼 수 있습니다.

샘플로 OpenStreetMap.France를 적용해보도록 하겠습니다.

빨간 네모칸에서 OpenStreetMap.France을 선택하면 초록 네모칸에 서버 정보가 표시됩니다.

‘https://{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png’

‘&copy; OpenStreetMap France | &copy; <a href=”https://www.openstreetmap.org/copyright”>OpenStreetMap</a> contributors’

이 두 부분만 코드에서 변경해주면 바로 적용이 됩니다.

2. 코드 적용

import { MapContainer, TileLayer, useMap, Marker, Popup } from "react-leaflet";
import "leaflet/dist/leaflet.css";
import { icon } from "leaflet";
const Icon = icon({
  iconUrl: "marker-icon.png",
  iconSize: [16, 16],
  iconAnchor: [12, 16],
});

const MyMap = () => {
  return (
    <MapContainer
      center={[37.56675, 126.97842]}
      zoom={10}
      scrollWheelZoom={true}
      style={{ width: "500px", height: "500px" }}
    >
      <TileLayer
        attribution='&copy; OpenStreetMap France | &copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
        url="https://{s}.tile.openstreetmap.fr/osmfr/{z}/{x}/{y}.png"
      />
      <Marker position={[37.56675, 126.97842]} icon={Icon}>
        <Popup>서울시청이에요.</Popup>
      </Marker>
    </MapContainer>
  );
};

export default MyMap;

설정에 따라 변경된 맵과 언어를 확인할 수 있습니다.

Next.js + Leaflet(OSM) Marker 표시하기

Next.js에서 Leaflet Marker 이미지 로딩하기

React-leaflet에서 제공하는 기본 설정 방법에 따라 leaflet 코드를 구현하여도 맵은 표시되지만 Marker 이미지는 깨져서 표시됩니다.

Next.js에서 이미지는 Next/image와 이미지 상대 주소를 import하여 사용하면 잘 로딩이 되지만 상대 경로 url을 직접 사용하면 작동하지 않습니다.

예를 들면 다음과 같은 상황입니다.

import Image from "next/image";
import logo from "../../styles/images/logo.png";

//작동함
<Image src={logo} alt="logo />

//작동하지 않음
<Image src={"../../styles/images/logo.png"} alt="logo" width="100px" height="100px" />

이는 Next.js에 정해진 폴더 규칙이 있기 때문인데요. 만약 경로를 사용해 이미지나 파일을 가져오고 싶다면 public 폴더를 이용해야 합니다.

빌드 후 기본 폴더는 public이므로 public 폴더 내 logo.png 파일을 넣는 경우 /logo.png로 접근이 가능합니다.

import Image from "next/image";

<Image src={"/logo.png"} alt="logo" width={"100px"} height={"100px"} />

Marker 이미지 표시하기

이를 참고하면 Marker 이미지 부분도 icon을 사용해서 응용할 수 있습니다.

public 폴더 내 images 폴더를 만들고 logo.png 파일을 넣은 뒤 다음과 같이 사용합니다.

import { MapContainer, TileLayer, useMap, Marker, Popup } from "react-leaflet";
import "leaflet/dist/leaflet.css";
import { icon } from "leaflet";

const Icon = icon({
  iconUrl: "images/logo.png",
  iconSize: [24, 24],
  iconAnchor: [12, 24],
});

const MyMap = () => {
  return (
    <MapContainer
      center={[37.56675, 126.97842]}
      zoom={10}
      scrollWheelZoom={true}
      style={{ width: "500px", height: "500px" }}
    >
      <TileLayer
        attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
        url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
      />
      <Marker position={[37.56675, 126.97842]} icon={Icon} >
        <Popup>서울 시청</Popup>
      </Marker>
    </MapContainer>
  );
};

export default MyMap;

Next.js + Leaflet(OpenStreetMap) 초기 설정하기

Next.js와 leaflet이 만나기 위해서는 참고해야 할 사이트가 많습니다.

leafletjs.com
react-leaflet.js.org
openstreetmap.org

간략하게 정리해보겠습니다.


1. 라이브러리 설치

Next.js + Typescript는 설치되었다고 가정하겠습니다.(관련 포스팅 클릭)

npm i leaflet react-leaflet

Typescript 지원을 위한 라이브러리도 설치합니다.

npm i -D @types/leaflet

2. 코드 구현하기

다음과 같이 컴포넌트를 생성합니다.

import “leaflet/dist/leaflet.css”; 를 설정하지 않으면 맵이 깨져서 표시가 되고 style에 사이즈를 설정하지 않으면 하얀 화면만 나오니 두 부분 모두 주의해야 합니다.

scrollWheelZoom은 스크롤 확대/축소 기능을 설정합니다.

import { MapContainer, TileLayer, useMap, Marker, Popup } from "react-leaflet";
import "leaflet/dist/leaflet.css";

const MyMap = () => {
  return (
    <MapContainer
      center={[37.56675, 126.97842]}
      zoom={10}
      scrollWheelZoom={true}
      style={{ width: "500px", height: "500px" }}
    >
      <TileLayer
        attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
        url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
      />
      <Marker position={[37.56675, 126.97842]}>
        <Popup>서울 시청</Popup>
      </Marker>
    </MapContainer>
  );
};

export default MyMap;

별도의 컴포넌트를 생성하는 이유는 관리를 위한 분리도 있지만 Next.js의 특성상 서버 렌더링 시 window 전역 객체에 접근할 수 없는 문제로 인해 발생하는 에러를 해결하기 위해서입니다.

DOM이 생성된 뒤 실행되는 useEffect를 사용하거나 레이지 로딩 기능인 dynamic을 사용할 수 있으며 여기서는 dynamic 기능을 사용하겠습니다.

같은 위치에 다음 컴포넌트를 생성합니다.

import dynamic from "next/dynamic";

const MyMap = dynamic(() => import("./MyMap"), { ssr: false });

const ShowMap = () => {
  return <MyMap />;
};

export default ShowMap;

이것으로 기본 구현은 완료되었으며 다음과 같이 맵을 호출하면 됩니다.

<ShowMap />

결과는 다음과 같습니다.

이것으로 Next.js에서 Leaflet을 사용하기 위한 설정이 완료되었지만 Marker 이미지가 깨지는 현상이나 언어 설정 등 추가할 부분이 많습니다.

해당 내용은 다른 포스트에서 다루도록 하겠습니다.