0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

React + Leafletで地図表示・マーカー・ポップアップ・現在地取得を試してみる

0
Last updated at Posted at 2026-07-15

はじめに

Reactで地図を表示したい場合、選択肢の一つにLeafletがあります。

Leafletは、ブラウザ上にインタラクティブな地図を表示できるJavaScriptライブラリです。

Reactで使う場合は、React向けのラッパーであるReact Leafletを使うと、MapContainerTileLayerMarkerPopup などをReactコンポーネントとして扱えます。

この記事では、React + Leafletで以下を試します。

  • 地図を表示する
  • OpenStreetMapのタイルを表示する
  • マーカーを表示する
  • ポップアップを表示する
  • 複数マーカーを配列から表示する
  • 地図クリックでマーカーを追加する
  • 現在地を取得して地図を移動する

目的は、地図アプリを作り込むことではなく、ReactでLeafletを使うときの基本的な機能を一通り確認することです。

サンプルコード

この記事で確認したサンプルコードは以下に置いています。

GitHubのソースコード

検証環境

この記事は、以下の環境で動作確認しています。

OS: Windows 11
Node.js: 24.13.0
npm: 11.6.2
Vite: 8.1.1
React: 19.2.7
TypeScript: 6.0.2
Leaflet: 1.9.4
React Leaflet: 5.0.0
Browser: Google Chrome

今回作るもの

今回は、以下のような簡単な地図画面を作ります。

- 東京駅周辺を初期表示する
- 複数の地点にマーカーを表示する
- マーカーをクリックするとポップアップを表示する
- 地図をクリックすると、その地点にマーカーを追加する
- ボタンを押すと現在地を取得して地図を移動する

地図タイルには、学習用としてOpenStreetMapのタイルを使います。

前提

この記事では、Vite + React + TypeScriptのプロジェクトを前提にします。

プロジェクトを作成していない場合は、以下で作成できます。

npm create vite@latest react-leaflet-map-sample -- --template react-ts

作成後、ディレクトリに移動します。

cd react-leaflet-map-sample

依存関係をインストールします。

npm install

LeafletとReact Leafletをインストールする

LeafletとReact Leafletをインストールします。

npm install leaflet react-leaflet

Leafletの型定義もインストールします。

npm install -D @types/leaflet

ファイル構成

今回の主なファイル構成は以下です。

src/
├─ App.tsx
├─ main.tsx
├─ index.css
├─ data/
│  └─ locations.ts
└─ components/
   ├─ MapView.tsx
   └─ CurrentLocationButton.tsx

LeafletのCSSを読み込む

Leafletを使うには、LeafletのCSSを読み込む必要があります。

src/main.tsx で読み込みます。

src/main.tsx
import { StrictMode } from 'react';
import { createRoot } from 'react-dom/client';
import 'leaflet/dist/leaflet.css';
import './index.css';
import App from './App';

createRoot(document.getElementById('root')!).render(
  <StrictMode>
    <App />
  </StrictMode>,
);

leaflet/dist/leaflet.css を読み込まないと、地図やマーカーの表示が崩れることがあります。

表示する地点データを用意する

まず、地図上に表示する地点データを用意します。

src/data/locations.ts
export type Location = {
  id: number;
  name: string;
  description: string;
  position: [number, number];
};

export const locations: Location[] = [
  {
    id: 1,
    name: '東京駅',
    description: '東京の主要ターミナル駅です。',
    position: [35.681236, 139.767125],
  },
  {
    id: 2,
    name: '皇居外苑',
    description: '東京駅からも近い広い公園エリアです。',
    position: [35.680959, 139.758761],
  },
  {
    id: 3,
    name: '日本橋',
    description: '歴史ある橋と商業エリアです。',
    position: [35.684095, 139.774503],
  },
];

Leafletでは、位置情報を [緯度, 経度] の形で扱います。

現在地取得ボタンを作る

現在地取得用のボタンを作ります。

React Leafletでは、useMap を使うと、現在のLeafletのMapインスタンスを取得できます。

src/components/CurrentLocationButton.tsx
import { useState } from 'react';
import { useMap } from 'react-leaflet';

type CurrentLocationButtonProps = {
  onLocationFound: (position: [number, number]) => void;
};

export function CurrentLocationButton({
  onLocationFound,
}: CurrentLocationButtonProps) {
  const map = useMap();
  const [isLoading, setIsLoading] = useState(false);

  const handleClick = () => {
    if (!navigator.geolocation) {
      alert('このブラウザでは現在地取得がサポートされていません。');
      return;
    }

    setIsLoading(true);

    navigator.geolocation.getCurrentPosition(
      (position) => {
        const currentPosition: [number, number] = [
          position.coords.latitude,
          position.coords.longitude,
        ];

        onLocationFound(currentPosition);
        map.flyTo(currentPosition, 15);
        setIsLoading(false);
      },
      () => {
        alert('現在地を取得できませんでした。');
        setIsLoading(false);
      },
    );
  };

  return (
    <button type="button" className="location-button" onClick={handleClick}>
      {isLoading ? '取得中...' : '現在地へ移動'}
    </button>
  );
}

navigator.geolocation.getCurrentPosition を使うと、ブラウザの現在地取得機能を利用できます。

現在地の取得時には、ブラウザ側で位置情報の許可ダイアログが表示されます。

取得した緯度・経度は、map.flyTo() に渡して地図を移動しています。

map.flyTo(currentPosition, 15);

地図コンポーネントを作る

次に、地図本体を作ります。

src/components/MapView.tsx
import { useState } from 'react';
import { MapContainer, Marker, Popup, TileLayer, useMapEvents } from 'react-leaflet';
import type { LatLngExpression } from 'leaflet';
import { locations } from '../data/locations';
import { CurrentLocationButton } from './CurrentLocationButton';

type AddedMarker = {
  id: number;
  position: [number, number];
};

function ClickMarker({
  onAddMarker,
}: {
  onAddMarker: (position: [number, number]) => void;
}) {
  useMapEvents({
    click(event) {
      onAddMarker([event.latlng.lat, event.latlng.lng]);
    },
  });

  return null;
}

export function MapView() {
  const initialPosition: LatLngExpression = [35.681236, 139.767125];

  const [addedMarkers, setAddedMarkers] = useState<AddedMarker[]>([]);
  const [currentPosition, setCurrentPosition] = useState<[number, number] | null>(
    null,
  );

  const handleAddMarker = (position: [number, number]) => {
    setAddedMarkers((markers) => [
      ...markers,
      {
        id: Date.now(),
        position,
      },
    ]);
  };

  return (
    <div className="map-wrapper">
      <MapContainer
        center={initialPosition}
        zoom={14}
        scrollWheelZoom
        className="map-container"
      >
        <TileLayer
          attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
          url="https://tile.openstreetmap.org/{z}/{x}/{y}.png"
        />

        {locations.map((location) => (
          <Marker key={location.id} position={location.position}>
            <Popup>
              <strong>{location.name}</strong>
              <div>{location.description}</div>
            </Popup>
          </Marker>
        ))}

        {addedMarkers.map((marker) => (
          <Marker key={marker.id} position={marker.position}>
            <Popup>
              <strong>追加した地点</strong>
              <div>緯度: {marker.position[0].toFixed(5)}</div>
              <div>経度: {marker.position[1].toFixed(5)}</div>
            </Popup>
          </Marker>
        ))}

        {currentPosition && (
          <Marker position={currentPosition}>
            <Popup>現在地</Popup>
          </Marker>
        )}

        <ClickMarker onAddMarker={handleAddMarker} />
        <CurrentLocationButton onLocationFound={setCurrentPosition} />
      </MapContainer>
    </div>
  );
}

MapContainer が地図全体を表すコンポーネントです。

<MapContainer center={initialPosition} zoom={14} scrollWheelZoom>

TileLayer では、地図タイルのURLと attribution を指定します。

<TileLayer
  attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
  url="https://tile.openstreetmap.org/{z}/{x}/{y}.png"
/>

MarkerPopup を組み合わせると、地図上にマーカーを表示し、クリック時にポップアップを表示できます。

<Marker position={location.position}>
  <Popup>
    <strong>{location.name}</strong>
    <div>{location.description}</div>
  </Popup>
</Marker>

地図クリックでマーカーを追加する

地図クリック時のイベントは、useMapEvents を使って取得しています。

function ClickMarker({
  onAddMarker,
}: {
  onAddMarker: (position: [number, number]) => void;
}) {
  useMapEvents({
    click(event) {
      onAddMarker([event.latlng.lat, event.latlng.lng]);
    },
  });

  return null;
}

event.latlng.latevent.latlng.lng から、クリックした地点の緯度・経度を取得できます。

取得した座標を addedMarkers に追加し、地図上にマーカーとして表示しています。

また、useMapEventsはReactコンポーネントの内部で呼ぶ必要があるため、DOMには何も表示しないコンポーネントとして定義し、MapContainerの子要素に配置しています

Appから地図を表示する

App.tsx から MapView を表示します。

src/App.tsx
import { MapView } from './components/MapView';

export default function App() {
  return (
    <main className="app">
      <section className="content">
        <h1>React + Leaflet Map Sample</h1>
        <p>
          地図表示、マーカー、ポップアップ、クリック地点の追加、現在地取得を試すサンプルです。
        </p>
      </section>

      <MapView />
    </main>
  );
}

CSSを追加する

地図の高さを指定しないと、地図が表示されないことがあります。

src/index.css に以下を追加します。

src/index.css
* {
  box-sizing: border-box;
}

body {
  margin: 0;
  font-family:
    system-ui,
    -apple-system,
    BlinkMacSystemFont,
    'Segoe UI',
    sans-serif;
  background: #f8fafc;
  color: #0f172a;
}

button {
  font: inherit;
}

.app {
  min-height: 100vh;
  padding: 24px;
}

.content {
  max-width: 960px;
  margin: 0 auto 16px;
}

.content h1 {
  margin-bottom: 8px;
  font-size: 28px;
}

.content p {
  margin-top: 0;
  color: #475569;
}

.map-wrapper {
  position: relative;
  max-width: 960px;
  margin: 0 auto;
}

.map-container {
  height: 560px;
  width: 100%;
  overflow: hidden;
  border: 1px solid #cbd5e1;
  border-radius: 12px;
}

.location-button {
  position: absolute;
  z-index: 1000;
  top: 16px;
  right: 16px;
  border: none;
  border-radius: 999px;
  background: #0f172a;
  color: #fff;
  padding: 10px 16px;
  cursor: pointer;
  box-shadow: 0 8px 20px rgb(15 23 42 / 20%);
}

.location-button:hover {
  background: #1e293b;
}

Leafletの地図は、親要素に高さがないと表示されません。

そのため、.map-containerheight: 560px; を指定しています。

動作確認

開発サーバーを起動します。

npm run dev

ブラウザで表示します。

http://localhost:5173

以下を確認します。

  • 地図が表示される
  • 東京駅、皇居外苑、日本橋のマーカーが表示される
  • マーカーをクリックするとポップアップが表示される
  • 地図をクリックするとマーカーが追加される
  • 「現在地へ移動」を押すと、位置情報の許可ダイアログが表示される
  • 許可すると現在地に地図が移動する

react-leaflet-map-sample.gif

現在地取得について

現在地取得には、ブラウザのGeolocation APIを使っています。

ブラウザによっては、HTTPSまたは localhost でないと現在地取得が使えない場合があります。

今回のようにローカル開発環境で http://localhost:5173 を使う場合は、動作確認しやすいです。

ただし、本番環境で使う場合はHTTPSで配信する必要があります。

また、現在地は個人情報に近い扱いになるため、実務で使う場合は、利用目的の説明やプライバシー面の配慮も必要です。

OpenStreetMapタイル利用時の注意

この記事では、学習用としてOpenStreetMapのタイルを使っています。

<TileLayer
  attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
  url="https://tile.openstreetmap.org/{z}/{x}/{y}.png"
/>

ただし、tile.openstreetmap.org はOpenStreetMap Foundationが運用している共有のタイル配信サーバーです。OpenStreetMapの地図データ自体は利用しやすい一方で、公式のタイルサーバーを大量アクセス前提の商用サービスでそのまま使っていいわけではありません。

小さな検証や学習用途では扱いやすいですが、実運用で多くのユーザーが地図を表示する場合は、OpenStreetMapのタイル利用ポリシーを確認する必要があります。

本番サービスで地図を使う場合は、アクセス数や用途に応じて、商用向けのタイル配信サービス、地図API、自前のタイルサーバーなども検討します。

よくあるエラー

地図が表示されない

LeafletのCSSを読み込んでいるか確認します。

import 'leaflet/dist/leaflet.css';

また、地図コンテナに高さが指定されているか確認します。

.map-container {
  height: 560px;
}

現在地を取得できない

ブラウザで位置情報の利用を許可しているか確認します。

また、現在地取得はHTTPSまたは localhost などの安全なコンテキストでないと使えない場合があります。

ボタンが地図の下に隠れる

Leafletの地図上にボタンを重ねる場合、z-index を指定します。

.location-button {
  z-index: 1000;
}

実務で追加で考えたいこと

今回のサンプルは、React + Leafletの基本機能を確認するための最小構成です。

実務で使う場合は、さらに以下のような観点を考える必要があります。

  • 店舗や施設データをAPIから取得する
  • 地図の表示範囲に応じてデータを絞り込む
  • マーカーが多い場合はクラスタリングする
  • 現在地を保存するかどうかを検討する
  • 位置情報の利用目的を明示する
  • 地図タイルの利用規約を確認する
  • スマートフォンでの操作性を確認する
  • 検索機能や絞り込み機能を追加する
  • GeoJSONでエリアやルートを表示する
  • 本番環境ではタイルサービスの選定を行う

地図UIは、店舗一覧、施設検索、配送エリア、不動産物件マップ、訪問先管理など、さまざまな業務画面に応用できます。

まとめ

今回は、React + Leafletで地図表示・マーカー・ポップアップ・現在地取得を試しました。

確認したことは以下です。

  • LeafletとReact Leafletをインストールする
  • LeafletのCSSを読み込む
  • MapContainer で地図を表示する
  • TileLayer でOpenStreetMapのタイルを表示する
  • MarkerPopup で地点情報を表示する
  • 配列データから複数マーカーを表示する
  • useMapEvents で地図クリックを検知する
  • Geolocation APIで現在地を取得する
  • useMapmap.flyTo() で地図を移動する

React Leafletを使うと、Leafletの地図機能をReactコンポーネントとして扱えます。

まずは、地図表示、マーカー、ポップアップ、現在地取得を押さえておくと、店舗一覧や施設検索のような地図UIにも広げやすい構成です。

参考

0
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?