Leafletは Webブラウザ上に地図を載せられるオープンソースのJavaScriptライブラリです。そして今回紹介する「React-Leaflet」は、Leafletを Reactからコンポーネント思考で使えるようにした拡張ライブラリです。
この記事では「React-Leaflet」を使って、地図をブラウザ上に表示させることや、簡単なイベント処理について紹介します。
スポンサーリンク
インストール
npmの人
npm install react-leaflet
yarnの人
yarn add react-leaflet
TypeScriptの場合
TypeScirptを使用している場合は、追加で以下もインストール
npm install -D @types/leaflet
yarn add -D @types/leaflet
基本のマップ表示
TypeScriptのコード例で、基本のマップ表示をしてみる。
まず、MapContainer
タグに地図中央に表示する緯度・経度や、ズーム率や style
で要素のサイズなどを指定していく。
その下には、TileLayer
タグを置き、表示する地図タイルを指定する。メジャーなところで、地図タイルには「OpenStreetMap」や「日本地理院」などのがある。(以下の例では OpenStreetMapの地図タイルを使用している)
import { MapContainer, TileLayer } from 'react-leaflet';
import { LatLngTuple } from 'leaflet';
function App() {
//地図の中央に表示する緯度・経度
const position: LatLngTuple = [35.710179001728534, 139.8107304222906]
return (
<MapContainer center={position} zoom={17} scrollWheelZoom={false} style={{ height: "100vh" }}>
<TileLayer
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
</MapContainer>
)
}
export default App;
■実行結果
マーカーを表示する
指定した緯度・経度の場所に、マーカーを表示してみます。
<Marker>
タグを挿入し、position
属性にマーカーを表示する緯度・経度を指定します。また、<Popup>
タグには、マーカーがクリックされた際に表示するポップアップメッセージを設定します。
function App() {
const position: LatLngTuple = [35.710179001728534, 139.8107304222906]
return (
<MapContainer center={position} zoom={17} scrollWheelZoom={false} style={{ height: "100vh" }}>
<TileLayer
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
{/* マーカーの表示 */}
<Marker position={[35.71042994800952, 139.80907162872504]}>
<Popup>
A pretty CSS3 popup. <br /> Easily customizable.
</Popup>
</Marker>
</MapContainer>
)
}
■実行結果
あれっ、マーカーアイコンの画像がリンクエラーになり表示されていません。。。
この現象がバグなのかは不明ですが、「React-Leaflet」ではフォルトのマーカーアイコンを設定しておく必要があるため、次のようにコンポーネントを宣言する前に、デフォルトのマーカーアイコンを設定する処理を追加します。
//マーカーのデフォルトアイコンを設定
let DefaultIcon = Leaflet.icon({
iconUrl: icon,
shadowUrl: iconShadow,
});
Leaflet.Marker.prototype.options.icon = DefaultIcon;
function App() {
const position: LatLngTuple = [35.710179001728534, 139.8107304222906]
return (
<MapContainer center={position} zoom={17} scrollWheelZoom={false} style={{ height: "100vh" }}>
<TileLayer
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
{/* マーカーの表示 */}
<Marker position={[35.71042994800952, 139.80907162872504]}>
<Popup>
A pretty CSS3 popup. <br /> Easily customizable.
</Popup>
</Marker>
</MapContainer>
)
}
無事、マーカーアイコンが表示されました。
■実行結果
スポンサーリンク
マップのクリックイベントを実装
地図上でクリックした所に、マーカーを表示するサンプルを作ってみます。
React-Leafletでは、マウス操作など地図上で発生するイベントは useMapEvents
フックを使って取得します。
最初に、useMapEvents
は MapContainer
のコンテキスト内でのみ使用可能であるため、クリックイベントを受け付けるサブコンポーネントを作ります。
import { LatLng } from 'leaflet'
import { useState } from 'react'
import { Marker, Popup, useMapEvents } from 'react-leaflet'
function ClickMaker() {
const [position, setPosition] = useState<LatLng | null>(null)
const map = useMapEvents({
click(e) {
setPosition(e.latlng)
}
})
return (
<>
{position && (
<Marker position={position}>
<Popup>You are here</Popup>
</Marker>
)}
</>
)
}
export default ClickMaker
上で作成した ClickMaker
を親の App
コンポーネントに配置します。
function App() {
const position: LatLngTuple = [35.710179001728534, 139.8107304222906]
return (
<MapContainer center={position} zoom={17} scrollWheelZoom={false} style={{ height: "100vh" }}>
<TileLayer
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
{/* マーカーの表示 */}
<ClickMaker/>
</MapContainer>
)
}
■実行結果
図形を書く
地図上に図形を書くサンプルコードを作って見ます。
四角形(Rectangle)
<MapContainer center={position} zoom={17} scrollWheelZoom={false} style={{ height: "100vh" }}>
<TileLayer
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<Rectangle
bounds={[
[35.710925125032475, 139.8085439015381],
[35.709940717542715, 139.81317875874197]
]}
pathOptions={ { color: "red" }}
/>
</MapContainer>
■実行結果
円(Circle)
center
に円の中央の緯度・経度を指定。radius
には円の半径をメートル単位で指定する。
<MapContainer center={position} zoom={17} scrollWheelZoom={false} style={{ height: "100vh" }}>
<TileLayer
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<Circle
center={[35.710925125032475, 139.8085439015381]}
pathOptions={ {fillColor: 'blue'} }
radius={100}
/>
</MapContainer>
■実行結果
ポリライン
positions
に一連の線で結ぶ緯度・経度を配列で指定する。
<MapContainer center={position} zoom={17} scrollWheelZoom={false} style={{ height: "100vh" }}>
<TileLayer
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<Polyline
pathOptions={{ color: 'purple', weight: 10 }}
positions={[
[35.70970550333407, 139.80904279241705],
[35.70961838678436, 139.8098903704621],
[35.709940717542715, 139.81296954639802],
[35.71013237305134, 139.81329141147842],
[35.710820586761024, 139.81380639560706],
[35.710419855450226, 139.81458960063608]
]} />
</MapContainer>
謎の線になってしまいましたが、こんな感じで自由に座標を指定して線を結ぶことができます。
■実行結果
ポリゴン(Polygon)
positions
に多角形の頂点となる緯度・経度を指定する。
<MapContainer center={position} zoom={17} scrollWheelZoom={false} style={{ height: "100vh" }}>
<TileLayer
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
<Polygon
pathOptions={{ color: "red" }}
positions={[
[35.71108053847581, 139.80874263954215],
[35.712050935021, 139.81003687451388],
[35.71173578472511, 139.8123484064776],
[35.71087720566655, 139.81086432991802]
]}
/>
</MapContainer>
■実行結果
スポンサーリンク
住所検索
住所・施設名などのキーワードから位置情報を検索し、見つかった場所を地図に表示するサンプルコードを作ってみます。「React-Leaflet」自体に住所検索をする機能はないため、今回、国土地理院のジオコーディング APIを使ってキーワードから緯度・経度を取得します。
まず、見つかった緯度・経度の場所に地図の表示を切り替えるコンポーネントを作成します。
/**
* マップの表示位置をパラメータで指定した緯度・経度に切り替えるためのコンポーネント
*/
function MapViewControl(prop: {position: LatLngTuple}) {
const map = useMap()
useLayoutEffect(() => {
map.setView(prop.position)
}, [prop.position])
return (null)
}
メインのコンポーネントには、住所検索エリアと、マップ表示の MapContainer
を置き、その下にタイルと先ほど作成した MapViewControl
コンポーネントを配置します。
function App() {
const [position, setPosition] = useState<LatLngTuple>([35.710304502694505, 139.81029660578832])
const [address, setAddress] = useState("")
//住所検索
const onSearch = async () => {
//処理内容は後述
}
return (
<>
{/* 住所検索エリアを追加 */}
<div>
<input
type="text"
style={ {width: "30rem"} }
value={address}
onChange={ e => setAddress(e.target.value) }
/>
<button onClick={onSearch}>住所検索</button>
</div>
<MapContainer center={position} zoom={17} scrollWheelZoom={false} style={{ height: "100vh" }}>
<TileLayer
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
{/* マップの表示位置を切り替えるコンポーネントを配置 */}
<MapViewControl position={position}/>
</MapContainer>
</>
)
}
最後に、上の onSearch
関数の中身を完成させます。
国土地理院のジオコーディング APIを使って、キーワードにヒットした住所の緯度・経度に、地図の表示を切り替える処理を実装します。
const onSearch = async () => {
//「国土地理院API」でキーワードから緯度・経度を含む住所情報を取得
const url = `https://msearch.gsi.go.jp/address-search/AddressSearch?q=${encodeURIComponent(address)}`
const response = await fetch(url);
const results = await response.json()
if (Array.isArray(results) && results.length > 0) {
//見つかった住所(施設)の位置を表示
const coordinates = results[0].geometry.coordinates
setPosition([coordinates[1], coordinates[0]])
} else {
alert("Not Found")
}
}
これで完成です。実際に動かして見ます。
【実行結果】
住所検索前の表示(スカイツリーを表示)
「ディズニーランド」で検索
うん、いいですね。
ちなみに、国土地理院のジオコーディング APIは、“渋谷3丁目” のような基本的な住所検索と、キーワード検索であれば、官公庁やディズニーランドのような有名な施設であれば検索が可能である。ただし、“セブンイレブン渋谷3丁目明治通り店” のようなローカルな施設の検索には対応していないため、精度の高い住所検索を求めるのなら、有料の Google Maps API を使う必要がある。
ルート表示
指定した地点間のルート表示を「react-leaflet-routing-machine」を使ってお手軽に作ってみます。
以下をインストールします。
npm i react-leaflet-routing-machine
TypeScriptの人はこちらもインストール
npm i @types/leaflet-routing-machine
ルート表示実装
まず、2つの地点間のルート表示をするコンポーネントを作ります。
今回のサンプルコードでは、浅草神社からスカイツリーまでのルートを求めています。
import Leaflet from "leaflet";
import { createControlComponent } from "@react-leaflet/core";
import "leaflet-routing-machine";
import "leaflet-routing-machine/dist/leaflet-routing-machine.css";
/**
* ルート表示用のコントール
*/
const createRoutineMachineLayer = (props: any) => {
console.log(props)
const instance = Leaflet.Routing.control({
waypoints: [
Leaflet.latLng(35.71498168439901, 139.79663181249592),
Leaflet.latLng(35.71020351730888, 139.81066512955994)
],
lineOptions: {
styles: [
{
color: "blue",
opacity: 0.6,
weight: 4
},
],
extendToWaypoints: true,
missingRouteTolerance: 1,
}
});
return instance;
};
const RoutingMachine = createControlComponent(createRoutineMachineLayer);
次にメインのコンポーネントに、上で作成した RoutingMachine
を配置します。
function App() {
const [position, setPosition] = useState<LatLngTuple>([35.710304502694505, 139.81029660578832])
return (
<MapContainer center={position} zoom={17} scrollWheelZoom={false} style={{ height: "100vh" }}>
<TileLayer
attribution='© <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a> contributors'
url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
/>
{/* ルート表示用のコントロール */}
<RoutingMachine/>
</MapContainer>
)
}
実行すると、次のようにルート上にラインが引かれ、右側に道順の案内が表示されます。
■実行結果
ルートの表示は問題なさそうだが、右側に表示される道順の案内の日本語がぶっ壊れていますね。
これは「react-leaflet-routing-machine」のデフォルトのルート探索エンジンが OSRM (Open Source Routing Machine) であるからでしょう。
0 件のコメント:
コメントを投稿