🗺️

【React.js + Leaflet】ReactLeafletで地図を表示しながらLeafletの理解を深める

2022/03/16に公開

はじめに

Leafletの恩恵を受けたい!!でもどうしてもプロジェクトに導入するにはラッパーを用いることが多い。とはいえLeafletの理解なしではいいかんじのコンポーネントもうまく使いこなせない・・・ということでLeafletのコードも書きつつReactLeafletを触ってみました。

Leafletについて

Leaflet

https://leafletjs.com/
OpenStreetMapなどのマップと組み合わせることによってWebで地図を表示することができるJavaScriptライブラリ

React Leaflet

https://react-leaflet.js.org/docs/core-architecture/
React.js上で使うためのラッパーライブラリでいいかんじに抽象化されたコンポーネントとして利用することができる

環境

  • leaflet v1.7.1
  • react-leaflet v3.2.5

導入から地図表示まで

導入からデフォルトの地図表示までをReactLeafletのチュートリアルを参考に進めていく

プロジェクト作成

npx create-react-app sample-react-leaflet

ライブラリインストール

npm install leaflet react-leaflet

コンポーネント作成

MapContainerでマップオブジェクトを作成し、TileLayerで地図を表示する。
レイヤーの概念がいくつかあり混同しやすいがここでは名前の通りタイル状に分割されたでOpenStreetMap地図データを表示するためTileLayerを用いている。

components/Map.js
import React from 'react';
import { MapContainer, TileLayer } from 'react-leaflet'
import './Map.css';

export const Map = () => {
  // 緯度軽度
  const position = [51.505, -0.09];
  // 初期マップズームレベル
  const zoom = 13;
  return (
    <MapContainer center={position} zoom={zoom}>
      <TileLayer
        attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
        url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
      />
    </MapContainer>
  )
};

高さを指定しないと地図が表示されない

components/Map.css
.leaflet-container {
    width: 600px;
    height: 300px;
    margin: 10px;
}

コンポーネントを表示

App.js
import 'leaflet/dist/leaflet.css';
import { Map } from "./components/Map";
function App() {
  return (
    <div className="App">
      <Map />
    </div>
  );
}

export default App;

起動

npm run start

ブラウザを確認

http://localhost:3000/にアクセスして表示を確認する🎉

Leafletでの実装

ReactLeafletではMapContainerTileLayerコンポーネンを使っているが実際はLeafletの実装を抽象化したものである。コンポーネントのPropsで渡していた値はオプション等で渡す必要がる(プラグインの読み込みやスタイルは省略)

index.html
<div id="map"></div>
<script>
  const position = [51.505, -0.09];
  const zoom = 13;
  let map = L.map('map', {
    center: position,
    zoom: zoom
  });
  L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',{
    attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
  }).addTo(map);
</script>

地図にマーカーとポップアップを表示

React Leaflet

そのままではMarker画像が表示されないので代替的にCDNから取得
MapContainerMarkerPopupを追加してマーカーとポップアップを表示します

component/MarkerWithPopup
import React from 'react';
import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet'
import './Map.css';
import L from 'leaflet'
L.Icon.Default.imagePath = 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/'

export const MarkerWithPopup = () => {
  const position = [51.505, -0.09];
  const zoom = 13;
  return (
    <MapContainer center={position} zoom={zoom}>
      <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={position}>
        <Popup>
          A pretty CSS3 popup. <br/> Easily customizable.
        </Popup>
      </Marker>
    </MapContainer>
  )
};

Image from Gyazo

Leaflet

<div id="map"></div>
<script>
  const position = [51.505, -0.09];
  const zoom = 13;
  let map = L.map('map', {
    center: position,
    zoom: zoom
  });
  L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',{
    attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
  }).addTo(map);
  let marker = L.marker(position).addTo(map)
  marker.bindPopup('A pretty CSS3 popup. <br/> Easily customizable.')
</script>

地図のマーカーをドラッグ可能にしてオプションを試す

地図のマーカーとポップアップに手を加えてマーカーをドラッグ可能にし、またドラッグ中は少しマーカーを透過、ドラッグを離したポイントの軽度緯度をポップアップに表示するようにする。

React Leaflet

components/DraggableMarker
import React, { useState, useRef } from 'react';
import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet'
import './Map.css';
import L from 'leaflet'
L.Icon.Default.imagePath = 'https://cdnjs.cloudflare.com/ajax/libs/leaflet/1.7.1/images/'

export const DraggableMarker = () => {
  const position = [51.505, -0.09];
  const zoom = 13;
  const [markerPosition, setMarkerPosition] = useState(L.latLng(position))
  const markerRef = useRef(null)
  const eventHandlers = {
    dragstart: () => {
      const marker = markerRef.current;
      marker.setOpacity(0.6);
    },
    dragend: () => {
      const marker = markerRef.current;
      marker.setOpacity(1);
      setMarkerPosition(marker.getLatLng());
    }
  }
  return (
    <MapContainer center={position} zoom={zoom}>
      <TileLayer
        attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
        url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
      />
      <Marker
        ref={markerRef}
        position={markerPosition}
        draggable={true}
        eventHandlers={eventHandlers}
      >
        <Popup>
          {`${markerPosition.lat}, ${markerPosition.lng}`}
        </Popup>
      </Marker>
    </MapContainer>
  )
};

eventHandlersのオブジェクトでイベント名と関数を渡している
dragstartdragendで指定しているのでマーカーを掴んだ時と離した時に関数が実行されている

  const eventHandlers = {
    dragstart: () => {
      const marker = markerRef.current;
      marker.setOpacity(0.6);
    },
    dragend: () => {
      const marker = markerRef.current;
      marker.setOpacity(1);
      setMarkerPosition(marker.getLatLng());
    }
  }

refでL.Markerオブジェクトを取得してL.MarkerのメソッドでOpacityを設定

const marker = markerRef.current;
marker.setOpacity(0.6);

L.MarkerのようなLeafletのオブジェクトに対して使えるオプションやイベントはLeafletのドキュメントから探す
getLatLng, setOpacity

Image from Gyazo

Leaflet

  const position = [51.505, -0.09];
  const zoom = 13;
  let map = L.map('map', {
    center: position,
    zoom: zoom
  });
  L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png',{
    attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors',
  }).addTo(map);
  let marker = L.marker(position, { draggable: true }).addTo(map)
  marker.bindPopup(`${marker.getLatLng().lat}, ${marker.getLatLng().lng}`)
  marker.on('dragstart', () => {
    marker.setOpacity(0.6);
  });
  marker.on('dragend', () => {
    marker.setOpacity(1);
    marker.bindPopup(`${marker.getLatLng().lat}, ${marker.getLatLng().lng}`)
  });

さいごに

いきなりReactLeafletで実装しようとおもうと色々すっ飛ばしすぎて不明なところが多かったのでExampleを参考にしつつ細かいところはLeafletのドキュメントを参考にするのがよさそうです

Discussion