🗺

React Leaflet + TypeScriptで地図(地理院タイル)を表示する

2021/10/09に公開
4

Webブラウザで地図を表示させる JavaScript のライブラリとして Leaflet がよく使われる。

https://leafletjs.com/

Leaflet ではブラウザに地図タイルを表示し、移動・拡大・縮小といった基本的な動作を行うとともに、マーカー・画像等を地図と連携して表示させることが可能。あちこちの商用サイトでも使用されているのを見かける。

Leaflet は単なる JavaScript のライブラリなので、これを React で扱うときは、その間を上手いことバインディングしてくれる React Leaflet を使うのが便利。

https://react-leaflet.js.org/

そこで本記事では、React Leaflet を TypeScript で扱いつつ、国土地理院が公開している地理院タイルを表示するまでの手順を解説する。

(というか、これをやる方法を軽く検索したけど良いサンプルが見付からず、試行錯誤して動くものを作ったので、その経緯をメモとして残しておく)

記事の変更履歴

  • 2022/03/07 @types のインストール方法を修正。(nakaakistさん、ありがとうございます)
  • 2021/10/17 タイトルを修正。座標取得方法のGoogle Mapsのやり方を修正。OpenStreetMapでのやり方を追加(@okuokuさん、ありがとうございます)
  • 2021/10/10 微修正

基本のコード

前提等

npm を使用できる環境が整っている前提とする。

サンプル実行環境: Windows 10
create-react-app 4.0.3
react-leaflet 3.2.1

プロジェクトの準備

まずは適当なプロジェクト(今回は leaflet-test という名前)を作成し、そのフォルダへ移動する。

npx create-react-app leaflet-test --template typescript
cd leaflet-test

次に必要なライブラリを取り込む。

npm i leaflet react-leaflet
npm i -D @types/leaflet

コードの記述

エディタでデフォルトのコードを以下のように書き換える。まずは公式サイトのサンプルを参考に記述してみる。

コードの解説は後述する。

src/App.tsx
import React from "react";
import { MapContainer, TileLayer } from "react-leaflet";
import { LatLng } from "leaflet";
import "leaflet/dist/leaflet.css";

import "./App.css";

function App() {
  const position = new LatLng(51.505, -0.09);

  return (
    <div className="App">
      <MapContainer center={position} zoom={13}>
        <TileLayer
          attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
          url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
        />
      </MapContainer>
    </div>
  );
}

export default App;
src/App.css
.App {
  text-align: center;
  height: 100vh;
}

.leaflet-container {
  width: 100vw;
  height: 100vh;
}

コードを書き換えたためにエラーが出ている src/App.test.tsx を消しておく。(お試し状態なのでとりあえずテスト不要ということで)

実行とコンパイルエラーの解消

この状態で実行しようとすると、エラーが出ると思われる。

npm start

エラーメッセージ:

Failed to compile.

./node_modules/@react-leaflet/core/esm/path.js 10:41
Module parse failed: Unexpected token (10:41)
File was processed with these loaders:
 * ./node_modules/babel-loader/lib/index.js
You may need an additional loader to handle the result of these loaders.
|   useEffect(function updatePathOptions() {
|     if (props.pathOptions !== optionsRef.current) {
>       const options = props.pathOptions ?? {};
|       element.instance.setStyle(options);
|       optionsRef.current = options;

原因は、エラーメッセージ中の > から始まる行で、ヌル合体演算子 ?? が使われているため。これはES2020で導入された演算子で、create-react-app のデフォルト状態では今のところトランスパイルできない(らしい)ため、エラーが出ている。このエラー箇所は React Leaflet のライブラリ内なので自分で書き換えるわけにもいかない。

これを解決するためには、package.json の一部(33行目あたりから)を以下のように書き換える。

修正前:

package.json
  "browserslist": {
    "production": [
      ">0.2%",
      "not dead",
      "not op_mini all"
    ],
    "development": [
      "last 1 chrome version",
      "last 1 firefox version",
      "last 1 safari version"
    ]
  },

修正後:

package.json
  "browserslist": [
    ">0.2%",
    "not dead",
    "not op_mini all"
  ],

トランスパイルの対象とするブラウザを変えることで解決している、らしい。

次に node_modeules/.cache ディレクトリを消す。これで先程トランスパイルに失敗したキャッシュが消える。

(このあと念のため、npm install を実行しておいても良い)

これでエラーは解消し、再度 npm start で実行すると、OpenStreetMap でロンドンの地図が表示される。

コードの解説

App.tsx

src/App.tsx
import { MapContainer, TileLayer } from "react-leaflet";
import { LatLng } from "leaflet";
import "leaflet/dist/leaflet.css";

必要なライブラリをインポートしている。
公式ドキュメントには書かれていないと思うけど、ここで leaflet.css をインポートしておくのがポイント。これは Leaflet デフォルトのスタイルを指定しているもので、これがないと表示が崩れる。

src/App.tsx
const position = new LatLng(51.505, -0.09);

初期表示するロンドン中心部の緯度と経度を指定している。公式サイトのサンプル(JavaScript)では単なる配列だけど、TypeScript を使うのできちんと座標オブジェクト(LatLng)にしている。

なお、座標がどんなものかについては後述する。

src/App.tsx
<MapContainer center={position} zoom={13}>
  <TileLayer
    attribution='&copy; <a href="http://osm.org/copyright">OpenStreetMap</a> contributors'
    url="https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png"
  />
</MapContainer>

MapContainer コンポーネントとして Leaflet の地図が表示される。center は最初に表示される中心の座標で、zoom はズームレベル(標準的には1~18程度)。

地図に対してマーカー等を追加する場合は、この子コンポーネントとして様々な要素を指定していく。

この単純な例では、TileLayer として OpenStreetMap の地図タイルを表示している。

App.css

src/App.css
.App {
  text-align: center;
  height: 100vh;
}

.leaflet-container {
  width: 100vw;
  height: 100vh;
}

まず .App に高さを指定して、縦を画面いっぱいにしている。CSSで高さ指定するときのよくある罠(高さがゼロになっていて表示されない問題)を回避するためのやつ。

.leaflet-container は、Leaflet の地図にデフォルトで指定されているクラス。実行されているページをブラウザの開発者ツールで見てみると分かる。
地図はこれでラップされているので、ここで表示の大きさを指定できる。ということでひとまず画面いっぱいに表示している。

地理院タイルを表示する

OpenStreetMap は世界中をカバーしているけど日本の地図ならやっぱり地理院タイルだよね、ということで国土地理院が公開している地理院タイルを表示してみる。

コードの変更

これは簡単で、TileLayer の部分の URL を変えるだけで良い。ついでに position の値を変えて、表示する座標を東京駅付近にしている。

src/App.tsx
...()...
function App() {
  const position = new LatLng(35.68, 139.76);

  return (
    <div className="App">
      <MapContainer center={position} zoom={13}>
        <TileLayer
          attribution='&copy; <a href="https://maps.gsi.go.jp/development/ichiran.html">国土地理院</a>'
          url="https://cyberjapandata.gsi.go.jp/xyz/std/{z}/{x}/{y}.png"
        />
      </MapContainer>
    </div>
  );
}
...()...

これで実行すれば、無事に地理院タイルが表示されて、ひとまず完成。

おまけ情報

座標について

Leaflet で扱う座標の数値はいわゆる DEG (DEGREE) 形式。すごくざっくり言えば、名前の通り「度」だけで座標を表すもの。

座標にはいくつか形式があって、変換がとてもややこしい組み合わせがあったり、同じ数値でも(時期によって測地系が異なるので)場所がズレていたりする。そんなこんなでとても複雑なので、興味のある人は調べてみると良いと思う。

まぁ要するに地球が「歪んだ球」の形をしているから面倒なわけで、地球が平面だったらいいのにね。

座標を調べる方法

どこかの場所(座標)を地図で表示したいときに指定する座標の調べ方はいくつかある。

本記事で書いたコードにちょっと手を加えれば取得できるけど、とりあえず他力本願で調べるのが手っ取り早い。

いくつも方法があるけど、それぞれ特徴があるので、利便性だったり思想信条で選べば良いと思う。

Googleマップ

まずはみんな大好き Googleマップを使う方法。普通の人なら、暇さえあれば Googleマップを開いて道を調べてたり渋滞情報を見たりしていると思うけど(え、しない?)、それで情報を取得できる。

やり方は簡単で、座標を知りたい場所を右クリックすると出てくるメニューの一番上に座標が出てくるので、これをクリックすると、座標がクリップボードにコピーされる。

ちなみにこの画像の結果では 35.7082872352298, 140.86999285778953 という値が入った。小数点以下は6桁もあれば実用上問題ない感じなので、これはすごく細かい座標まで指定していることになる。(計算してないけど、ミリメートル単位とかのレベル)

地理院地図

地理院地図を使用する方法もある。日本の地図の正確さ、という観点から見るとこちらが良いかも。

https://maps.gsi.go.jp/

地図をスクロールし、知りたい場所に画面中心の十字線を合わせると、画面下に情報が表示される。

もし表示されていなかったり、表示項目が少ない(標高のみ)のときは、一番左下の斜め矢印アイコンをクリックすると、情報の表示内容が変わる。

OpenStreetMap

名前の通りオープンデータである OpenStreetMap を使う方法もある。

https://www.openstreetmap.org/

座標を知りたい場所を右クリックして出てきたメニューから「アドレスを表示」を選ぶと、画面左に座標(35.7081, 140.87)が表示される。

桁数は表示倍率に依存するので、細い位置を知りたい場合はズームインすれば良い。

国土地理院の地図タイル

国土地理院が公開している地図タイルの情報は、以下のページに記載されている。

https://maps.gsi.go.jp/development/ichiran.html

ここに書いてあるURLをコードで指定すれば、簡単に Leaflet で表示できる。
まずは基本となるベースマップに標準地図と淡色地図があり、他に衛星写真や起伏図、あるいは災害関係のタイルも公開されており、幅広い。

まぁこれらを単に見るだけなら、自分でコードを書かずとも、国土地理院のサイトで上記の地理院地図を見れば良いだけではある。

地図への情報追加

自分でコードを書いて地図を扱うメリットは、そこに様々な情報を重ねて表示できること。Leaflet ではマーカーや図形等でそれを簡単に実現できるようになっている。

そのやり方は React Leaflet の公式ドキュメントの Examples に色々と書かれている。

https://react-leaflet.js.org/docs/example-popup-marker/

ただ、TypeScript で実装する場合は少し工夫が必要になったりする。
気が向いたらこのあたりのやり方についてそのうち解説を書くかも。

Discussion

okuokuokuoku

Web上のGoogle Mapsは右クリックで直ぐ座標がコピーできるので便利です

OpenStreetMap の場合は右クリックで"アドレスを表示"、Yahoo地図にいたっては住所取得が成功すると緯度経度が画面に出ないのでちょっと不便ですね。。

tristris

Googleマップにそんなやり方があったとは気付いていませんでした。いつも項目自体は目にしていたのに華麗にスルーしていた……。目からうろこです。
OSMも含め、のちほど追記させていただきます。

nakaakistnakaakist

めちゃめちゃ細かいですが、npm i -D @types/leaflet @types/react-leafletのところ、@types/react-leafletは不要かもと思いました
https://react-leaflet.js.org/docs/start-installation/#using-typescript

tristris

ご指摘、ありがとうございます。
確かにドキュメントを読むと「React Leafletはインストールされたパッケージの中にTypeScriptの定義があるが、Leafletの定義が必要なのでインストールしてね」と書いてありますね。
ということで記事を修正しました。