最近は地図関係のアプリを開発してて、ライブラリも多く実装したものを定期的にメモに残しておこうと思う
Google Map の API が高額でいろいろなレイヤーを重ね合わせるのが少し面倒そうだったので、OpenStreetMap を使う方法を探索した
OpenSteetMap を使えるライブラリはかなりたくさんあり色々試したが、本記事では React Leaflet でシンプルな地図を表示させたメモです
環境
- React 18.2.0
- leaflet 1.9.4
- react-leaflet: 4.2.1
React Leaflet を使う準備
$ npm i react-leaflet leaflet
$ npm i -D @types/leaflet
import { latLng } from "leaflet";
import { MapContainer, TileLayer } from "react-leaflet";
import "leaflet/dist/leaflet.css";
import "./map.css";
const position = latLng([35.3607411, 138.727262]);
const zoom = 5;
export const Map = (): JSX.Element => {
return (
<MapContainer center={position} zoom={zoom}>
<TileLayer
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors © <a href="https://carto.com/attributions">CARTO</a>'
{}
url="https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png"
maxZoom={13}
minZoom={2}
/>
</MapContainer>
);
};
.leaflet-container {
width: 100%;
height: 100vh;
}
これでシンプルな地図が表示できた。
ハマりどころは Leaflet の CSS (leaflet/dist/leaflet.css
) が必要なことと、Leaflet が出力するコンテナの高さがないと地図が表示されないこと。TypeScript だと position を latLng
関数を挟まないと Type Error になることくらい
クリックした箇所に marker を表示させる
useMapEvent
hook で地図上のイベントを取得できるので、クリックされた lat, long を取得して Marker として表示させる
import { useState } from "react";
import { LatLng, latLng, icon } from "leaflet";
import { Marker, Popup, useMapEvent } from "react-leaflet";
const ICON = icon({
iconUrl: "/images/leaflet/marker-icon.png",
iconSize: [25, 41],
iconAnchor: [12, 41],
popupAnchor: [0, -30],
});
export const LocationMarkers = (): JSX.Element => {
const [markers, setMarkers] = useState<LatLng[]>([]);
const map = useMapEvent("click", (e) => {
const { lat, lng } = e.latlng;
setMarkers((markers) => [...markers, latLng(lat, lng)]);
});
return (
<>
{markers.map((marker) => (
<Marker key={marker.toString()} position={marker} icon={ICON}>
<Popup>{marker.toString()}</Popup>
</Marker>
))}
</>
);
};
<MapContainer>
内に作成した <LocationMarkers />
コンポーネントを追加すれば OK
export const Map = (): JSX.Element => {
return (
<MapContainer center={position} zoom={zoom}>
<TileLayer
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors © <a href="https://carto.com/attributions">CARTO</a>'
url="https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png"
maxZoom={13}
minZoom={2}
/>
<LocationMarkers />
</MapContainer>
);
};
地図のクリックした箇所にマーカーが表示されマーカーをクリックするとポップアップが表示されるようになった!
🐞 デフォルトのマーカー画像が表示されない問題
Leaflet.Marker.prototype.options.icon
に Leaflet.icon
を渡す方法・Leaflet.Icon.Default.mergeOptions
にアイコン画像を設定する方法を issue でみつけたが leaflet v1.9.4
, react-leaflet v4.2.1
の環境では画像が壊れたままになってしまったので、カスタムアイコンを作って表示させた
h3-js で取得したエリアを React Leaflet で表示させる
h3geo.org
h3-js は Uber の開発した世界を六角形のグリッドで表すJavaScript のライブラリ。
h3-js で取得した六角形のエリアを lat, long の矩形 (polygon) として地図上に表示させてみます
$ npm i h3-js
h3 では世界をどのサイズの六角形のグリッド化しているのかというサイズを resolution という数字で表しています。
今回はマーカーと同じようにクリックした位置が含まれる resolution を Polygon 化して地図上にプロットします。
import { FC, useState } from "react";
import { LatLng } from "leaflet";
import { Polygon, useMapEvent } from "react-leaflet";
import { cellToBoundary, CoordPair, latLngToCell } from "h3-js";
const resolution = 3;
export const HexLayer: FC = () => {
const [hexagons, setHexagons] = useState<{index: string, polygon: CoordPair[]}[]>([]);
const map = useMapEvent("click", (e) => {
const { lat, lng } = e.latlng;
const h3Index = latLngToCell(lat, lng, resolution);
const boundary = cellToBoundary(h3Index);
setHexagons((hexagons) => [...hexagons, {
index: h3Index,
polygon: boundary,
}]);
});
return (
<>
{hexagons.map(({ index, polygon }) => (
<Polygon key={index} positions={polygon} />
))}
</>
);
}
h3-js で取得したエリアを Polygon として表示させることが出来ました!
※ Leaflet は左右に地図を移動させると経度が -600 や 1000 のように -180 - 180 の範囲を超えてしまうようです。h3-js の cellToBoundary
で変換した座標は -180 〜 180 の間に収まる数値に変換されるため、地図を移動させまくると Ploygon が表示されないようにみえてしまいます
Sample
所管
React-Leaflet の example には実例が色々とコードと共に載っているので、扱いやすいライブラリだなと感じました。
一方で Leaflet に依存しているので、ちょっとした問題があると Leaflet 側まで見に行かなければならない事もあるのは注意が必要そうです (経度が延々大きくも小さくもなる問題とか)
また React-Leaflet は地図上に表示させているマーカーや矩形を 画像や SVG といった HTML 要素で表現しているので表示量が多くなると Canvas ベースのものより処理が重くなるのではないかな〜という気がしています。
単体でできることが豊富なのでサクッと地図を使ったモックなどを作るのには向いてそうですね!
おわり
[参考]
13歳からの地政学 サクッと読めて面白かった!