TS + Next.JSでMapLibreの地図を使う方法

どうも、最近イコラブにもハマりづつあるベイファンのユエンです。

ReactでMapLibre GL JSを使う方法は「MapTiler」のドキュメントで公開されているんですが、MapTilerのほうのチュートリアルがJSになっていたり、NextJSに対応していないので、この記事では「NextJS」と「Typescript」を使った実装方法にこだわって実装します。

ただ地図のコンポーネントが欲しいだけの方は直接コピペして使ってください。

tsxファイル
```tsx
'use client';
import { useEffect, useRef } from 'react';
import maplibregl from 'maplibre-gl';
import 'maplibre-gl/dist/maplibre-gl.css';
import React from 'react';

type Props = {
    height: string;
    width: string;
};

export default function Map({ height, width }: Props) {
    const mapContainer = useRef<HTMLDivElement | null>(null);
    const map = useRef<maplibregl.Map | null>(null);

    useEffect(() => {
        if (map.current || !mapContainer.current) return;

        map.current = new maplibregl.Map({
            container: mapContainer.current,
            style: {
                version: 8,
                sources: {
                    'base-map': {
                        type: 'raster',
                        tiles: [
                            // 背景地図のURLは適宜置き換えてください
                            'https://tiles.stadiamaps.com/tiles/stamen_watercolor/{z}/{x}/{y}.jpg',
                        ],
                        tileSize: 256,
                        attribution:
                            'Map tiles by <a target="_blank" href="https://stamen.com">Stamen Design</a>; Hosting by <a href="https://stadiamaps.com/" target="_blank">Stadia Maps</a>. Data &copy; <a href="https://www.openstreetmap.org/about" target="_blank">OpenStreetMap</a> contributors',
                        // 背景地図置き換えたらアトリビューションも忘れずに
                    },
                },
                layers: [
                    {
                        id: 'base-tiles',
                        type: 'raster',
                        source: 'base-map',
                        minzoom: 0,
                        maxzoom: 22,
                    },
                ],
            },
            center: [139.63982660835788, 35.4434200783276],
            zoom: 12,
        });

        map.current.on('load', () => {
            if (!map.current) return;

            map.current.addSource('places', {
                type: 'geojson',
                data: {
                    type: 'FeatureCollection',
                    features: [
                        {
                            type: 'Feature',
                            properties: {
                                title: '横浜スタジアム',
                                description: '＼横浜優勝／',
                            },
                            geometry: {
                                type: 'Point',
                                coordinates: [
                                    139.63982660835788, 35.4434200783276,
                                ],
                            },
                        },
                    ],
                },
            });

            map.current.addLayer({
                id: 'places',
                type: 'circle',
                source: 'places',
                paint: {
                    'circle-radius': 10,
                    'circle-color': '#007cbf',
                    'circle-stroke-width': 2,
                    'circle-stroke-color': '#fff',
                },
            });
        });

        map.current.on('click', 'places', (e: maplibregl.MapMouseEvent) => {
            console.log(e);

            if (map.current === null) return;

            const coordinates = e.lngLat;
            const popup = new maplibregl.Popup();

            popup
                .setLngLat(coordinates)
                .setHTML('<p style="color:black">＼横浜優勝／</p>')
                .addTo(map.current);
        });

        if (!map.current) return;
    }, []);

    return (
        <div
            ref={mapContainer}
            style={{
                height: height,
                width: width,
            }}
        ></div>
    );
}

```

詳しくハマりポイントについて説明します。
1. React系のライブラリでは地図が副作用として扱われています。`useEffect`フックと`useRef`を必ず使うことになるので、CSRできるようにしましょう
2. おそらく型でハマる人が多いのではないかと思います。地図は`Map`型で定義されるので、`maplibregl.Map`型を使いましょう
3. 地図コンテナの型は`HTMLDivElement`です
4. 地図のクリックイベントで呼び出されるのは`MapMouseEvent`です

正直型を定義する点とクライアントサイドレンダリングさせるあたり以外はMapTilerのドキュメントからそのままコピペしてもいいような気がする（なんなら型はCopilotがなんとかしてくれる）んですが、ググっても引っかからないかもしれないんで、とりあえず地図ライブラリを使う方法を載せておきます。

MapLibreはTSで実装するためのドキュメントが若干足りていない気がするので、もしMapLibreをメンテナンスされているMIERUNEのY.Kさんや、K.Iさんがこの記事を読んでいるのであれば、ぜひ型について（できればTSでの実装について）のドキュメントを追加することについて検討してください。