Open10

React Leaflet関連

iwareiiwarei

Reactで地図を扱いたかったので、メモ。
やりたいこと:
 ・地図上にマーカを表示する。また、マーカの色は分類別に異なる。
 ・地図上にマーカを追加もしくは削除する。
とりあえずこれぐらいできれば十分。

iwareiiwarei

導入

npm install react react-dom leaflet
npm install react-leaflet
npm install -D @types/leaflet
iwareiiwarei

手始めに、React LeafletのSetupをコピペして試してみる。

import React from 'react';
import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet';

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

しかし、こんな感じでタイルがバラバラに。

ググると、どうやらheight, widthとcssの読み込みが原因らしい。
Setupのページの下のほうにもよく見ると書いてあった。
そのため、下記のように修正。

import React from 'react';
import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet';
import 'leaflet/dist/leaflet.css'; // 追加

export const Home = () => {
  return (
    <div>
      <MapContainer
        center={[51.505, -0.09]}
        zoom={16}
        scrollWheelZoom={false}
        style={{ height: '100vh', width: '100vw' }} // 追加
      >
        <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={[51.505, -0.09]}>
          <Popup>
            A pretty CSS3 popup. <br /> Easily customizable.
          </Popup>
        </Marker>
      </MapContainer>
    </div>
  );
};


すると、いい感じに表示された!

iwareiiwarei

さきほど、地図が表示されのはいったん確認したが、マーカー(ピン)がうまく表示されない状態となっているので修正していく。
https://qiita.com/honda28/items/e4c73c916e4d9b2ec279
上記記事を参考に、修正。

import React from 'react';
import Leaflet from 'leaflet';
import { MapContainer, TileLayer, Marker, Popup } from 'react-leaflet';

import 'leaflet/dist/leaflet.css';
import icon from 'leaflet/dist/images/marker-icon.png'; // 追加
import iconShadow from 'leaflet/dist/images/marker-shadow.png'; // 追加

// 追加
const DefaultIcon = Leaflet.icon({
  iconUrl: icon,
  shadowUrl: iconShadow,
});
Leaflet.Marker.prototype.options.icon = DefaultIcon;

export const Home = () => {
  return (
    <div>
      <MapContainer
        center={[51.505, -0.09]}
        zoom={16}
        scrollWheelZoom={false}
        style={{ height: '100vh', width: '100vw' }}
      >
        <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={[51.505, -0.09]}>
          <Popup>
            A pretty CSS3 popup. <br /> Easily customizable.
          </Popup>
        </Marker>
      </MapContainer>
    </div>
  );
};

すると、マーカーが表示された!

iwareiiwarei

マーカーは表示されるようになったが、ポップアップとマーカーの中心がずれているのが気になる。

import React from 'react';
import Leaflet from 'leaflet';
import icon from 'leaflet/dist/images/marker-icon.png';
import iconShadow from 'leaflet/dist/images/marker-shadow.png';

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

const DefaultIcon = Leaflet.icon({
  iconUrl: icon,
  shadowUrl: iconShadow,
  popupAnchor: [12, 0],  //追加
});
Leaflet.Marker.prototype.options.icon = DefaultIcon;

export const Home = () => {
  return (
    <div>
      <MapContainer
        center={[51.505, -0.09]}
        zoom={16}
        // scrollWheelZoom={false}
        style={{ height: '100vh', width: '100vw' }}
      >
        <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={[51.505, -0.09]}>
          <Popup>
            A pretty CSS3 popup. <br /> Easily customizable.
          </Popup>
        </Marker>
      </MapContainer>
    </div>
  );
};

popupAnchorでポップアップの位置を調整してあげるといい感じになった。

iwareiiwarei

ここまでの内容で、すでに地図上にマーカを建てることは十分可能と思われるため、ここからはクリックした場所にマーカを建てていく。

import React, { useState } from 'react';
import Leaflet from 'leaflet';
import {
  MapContainer,
  TileLayer,
  Marker,
  Popup,
  useMapEvents,
} from 'react-leaflet';

import 'leaflet/dist/leaflet.css';
import icon from 'leaflet/dist/images/marker-icon.png';
import iconShadow from 'leaflet/dist/images/marker-shadow.png';

const DefaultIcon = Leaflet.icon({
  iconUrl: icon,
  shadowUrl: iconShadow,
  popupAnchor: [12, 0],
});
Leaflet.Marker.prototype.options.icon = DefaultIcon;

const AddMaker = () => {
  const [position, setPosition] = useState<any>();

  const map = useMapEvents({
    click(e) {
      setPosition(e.latlng);
      map.flyTo(e.latlng, map.getZoom());
    },
  });
  return !position ? null : (
    <Marker position={position}>
      <Popup>Add marker.</Popup>
    </Marker>
  );
};

export const Home = () => {
  return (
    <div>
      <MapContainer
        center={[51.505, -0.09]}
        zoom={16}
        // scrollWheelZoom={false}
        style={{ height: '100vh', width: '100vw' }}
      >
        <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={[51.505, -0.09]}>
          <Popup>
            A pretty CSS3 popup. <br /> Easily customizable.
          </Popup>
        </Marker>
        <AddMaker />
      </MapContainer>
    </div>
  );
};

投げやりになってきたが、この内容で右クリっくした位置にマーカが建てられる。

iwareiiwarei

右クリしたいちにマーカを建てるようにしたが、マーカ画像の左上がクリックした位置となるため、実際にマーカを建てたくクリックした位置とマーカが建つ位置にずれが生じたように感じる、、

DefaultIconの定義にiconAnchorを追加してあげると、いい感じに調整できた。
また、iconAnchorの値をいじるとpopupAnchorにも影響するので、こちらも調整するといい感じになった。

const DefaultIcon = Leaflet.icon({
  iconUrl: icon,
  shadowUrl: iconShadow,
  iconAnchor: [12, 50],
  popupAnchor: [0, -50],
});

※値は使用しているデフォのアイコンのサイズが、25*41だったのでそれをもとにいろいろ試して様子見しながら適当に調整した。

https://leafletjs.com/examples/custom-icons/#creating-an-icon

iwareiiwarei

ピンをいくつでも建てることができるように修正。

import React, { useState } from 'react';
import Leaflet, { LatLng } from 'leaflet';
import {
  MapContainer,
  TileLayer,
  Marker,
  Popup,
  useMapEvents,
} from 'react-leaflet';

import 'leaflet/dist/leaflet.css';
import icon from 'leaflet/dist/images/marker-icon.png';
import iconShadow from 'leaflet/dist/images/marker-shadow.png';

const DefaultIcon = Leaflet.icon({
  iconUrl: icon,
  shadowUrl: iconShadow,
  iconAnchor: [12, 52],
  popupAnchor: [0, -52],
});
Leaflet.Marker.prototype.options.icon = DefaultIcon;

const AddMaker = () => {
  const [positions, setPositions] = useState<LatLng[]>([]);

  useMapEvents({
    click(e) {
      setPositions((prev) => [...prev, e.latlng]);
    },
  });

  return (
    <>
      {positions.map((position) => (
        <Marker position={position}>
          <Popup>Add marker.</Popup>
        </Marker>
      ))}
    </>
  );
};

export const NotifyPointMaster = () => {
  return (
    <div>
      <MapContainer
        center={[51.505, -0.09]}
        zoom={16}
        style={{ height: '100vh', width: '100vw' }}
      >
        <TileLayer
          attribution='&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
          url="https://mt1.google.com/vt/lyrs=r&x={x}&y={y}&z={z}"
        />
        <Marker position={[51.505, -0.09]}>
          <Popup>
            A pretty CSS3 popup. <br /> Easily customizable.
          </Popup>
        </Marker>
        <AddMaker />
      </MapContainer>
    </div>
  );
};