GROWI地図表示プラグインの紹介

[オープンソースのWikiであるGROWI](https://growi.org/ja/)にはプラグイン機能が用意されています。自社のデータを表示したり、表示をカスタマイズするのに利用できます。

今回は、GROWIプラグインとして作った地図表示プラグインを紹介します。

![FireShot Capture 654 - テストページ - GROWI - localhost 654.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/b84963c7-b601-2951-77fa-f5c7e8d4e7de.jpeg)

## プラグインを追加する

利用する際には、GROWIの管理画面の `プラグイン` にて追加してください。URLは `https://github.com/goofmint/growi-plugin-embed-map` です。

![Admin](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/f5b3e2d3-ccf3-d536-d59e-7301ea1b2f7e.jpeg)

## 使い方

利用する際には、ページの中で `$map` と書きます。記述した場所に地図が表示されます。引数として、住所または緯度経度が指定できます。

```
$map(東京都千代田区丸の内1-1-1)
```

または位置情報を直接指定することもできます。

```
$map(latitude=35.681236,longitude=139.767125)
```

地図はOpenStreetMapを利用しています。また、ジオコーディング（住所から緯度経度を取得）には[Nominatim](https://nominatim.openstreetmap.org/ui/search.html)（グローバル）と[国土地理院API](https://github.com/gsi-cyberjapan/gsimaps)を利用しています。

![FireShot Capture 653 - テストページ - GROWI - localhost 653.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/197026/672f1af3-be9d-7a62-ee8c-8ec725b418de.jpeg)

## コードについて

今回は以下のライブラリを利用しています。

- [Leaflet](https://leafletjs.com/)

そして、最初に `$map` と記述があれば、地図表示用のHTMLタグを出力します。

```tsx
// 1ページ中に複数の地図を表示できるようにするため、IDをランダム生成
const domId = `map-${Math.random().toString(36).slice(-8)}`;
if (n.type === 'leafGrowiPluginDirective' && n.name === 'map') {
	const address = Object.keys(n.attributes)[0]; // 住所
	let { latitude, longitude } = n.attributes; // 位置情報
	n.type = 'html';
	n.value = `<div id="${domId}" style="height: 400px; width: 100%"></div>`;
	let map: null | L.Map = null;
}
```

そして、このHTMLがレンダリングされた後に、地図を表示します。

```tsx
let map: null | L.Map = null;
const id = setInterval(async() => {
	// DOMが存在するか確認
	const mapDom = document.querySelector(`#${domId}`);
	if (mapDom == null) {
		return;
	}
	clearInterval(id);
	// すでに地図が表示されている場合は何もしない
	if (map != null) return;
	
	// 位置情報を取得する
	const geoPoint = await getGeoPoint(address, latitude, longitude);
	if (geoPoint === null) throw new Error(`Failed to get geo point ${address}`);
	latitude = geoPoint.latitude;
	longitude = geoPoint.longitude;

	// 地図表示
	map = L.map(domId).setView([parseFloat(latitude), parseFloat(longitude)], 13);
	L.tileLayer('https://{s}.tile.openstreetmap.org/{z}/{x}/{y}.png', {
		attribution: '&copy; <a href="https://www.openstreetmap.org/copyright">OpenStreetMap</a>',
	}).addTo(map);
	// マーカー表示
	const marker = L.marker([parseFloat(latitude), parseFloat(longitude)]);
	marker.bindPopup(address).openPopup();
	marker.addTo(map);
}, 1000);
```

位置情報の取得は以下のようになります。

```tsx
interface GeoPoint {
  longitude: string;
  latitude: string;
}

const getGeoPoint = async({ address, latitude, longitude }: any) => {
  if (latitude && longitude) {
    return {
      latitude,
      longitude,
    };
  }
  if (address == null) return null;
  const geoPointGSI = await getGeoPointByGSI(address as string);
  if (geoPointGSI) return geoPointGSI;
  return getGeoPointByOSM(address as string);
};

// Nominatimから位置情報を取得
const getGeoPointByOSM = async(address: string): Promise<GeoPoint | null> => {
  const url = `https://nominatim.openstreetmap.org/search?q=${encodeURIComponent(address)}&format=json`;
  const res = await fetch(url);
  const json = await res.json();
  if (json.length > 0) {
    return {
      longitude: json[0].lon,
      latitude: json[0].lat,
    };
  }
  return null;
};

// 国土地理院APIから位置情報を取得
const getGeoPointByGSI = async(address: string): Promise<GeoPoint | null> => {
  const res = await fetch(`https://msearch.gsi.go.jp/address-search/AddressSearch?q=${encodeURIComponent(address)}`);
  const json = await res.json();
  if (json.length > 0) {
    return {
      longitude: json[0].geometry.coordinates[0],
      latitude: json[0].geometry.coordinates[1],
    };
  }
  return null;
};
```

## まとめ

GROWIプラグインを使うと、表示を自由に拡張できます。足りない機能があれば、どんどん追加できます。ぜひ、自分のWikiをカスタマイズしましょう。

[OSS開発wikiツールのGROWI | 快適な情報共有を、全ての人へ](https://growi.org/ja/)