Maplibre GL JSを採用するならWebフロントエンドのフレームワークにはSvelteを採用するのも良いのではという話


:::note info
これは [MapLibre Advent Calendar 2024](https://qiita.com/advent-calendar/2024/maplibre) 21日目の記事です。
:::

## はじめに
みなさんはWebフロントエンドのフレームワークに何を採用しておられますでしょうか？
やっぱりReactが多いでしょうか？
確かにそうですね、使える人も多いし、多いからこそ周りのエコシステムがとても充実しています
ですが、何も考えずにReactを採用するよりは、用途によっては考え直しても良いのではと思うのです
今回はWebGISを作成する際、地図のライブラリーにMaplibre GL JSを採用するならば、Webフロントエンドのフレームワークに[Svelte](https://svelte.jp/)を採用すると幸せになれるかも
そういうお話になります

## 彗星のごとく登場したSvelte MapLibre GL
つい先日（2024年12月の話です）、Svelteのバージョンが上がり、大きい変更が入りました
それに呼応するように登場したのが本日紹介する[Svelte MapLibre GL](https://github.com/MIERUNE/svelte-maplibre-gl)というライブラリーになります
実は、すでに[svelte-maplibre](https://github.com/dimfeld/svelte-maplibre)というライブラリーがあったりします
きちんと使い比べたわけではありませんが、現状において大変使いやすいため（社内製のライブラリーであるという事もありますが）こちらを推させていただいております

詳しくは以下記事を御覧ください

https://qiita.com/ciscorn/items/6f97f681f31cafe513bd

## Maplibre GL JSでのちょっとした困りごと
これはMaplibre GL JSの仕様だと思うのですが、背景として設定した地図スタイルを何も考えず変更すると、重ねていたレイヤーも一緒に全て消えてしまうという動作をします

![Kapture 2024-12-21 at 10.44.51.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2788/927e691c-fd72-c8f7-b34c-38f78ca42367.gif)

重要なのは重ねたレイヤーの変更や比較であって、背景地図を変えることは無いという思想があると考えられます
そう言われると確かにそうかも…となるものの
結構背景地図のみの選択需要があったりしますよね…
これに対応するためにはレイヤーの状態を背景地図を変更する前に取っておいて、変更後に再反映するという実装が必要になります
これがまあ、私はよく忘れてハマることがあります（なんで消えるの！ってなる）

ソースコードは以下のような感じです（[Svelte MapLibre GLのExample](https://svelte-maplibre-gl.mierune.dev/examples/basestyle)を書き換えた感じになっています）
UIライブラリーは[shadcn-svelte](https://www.shadcn-svelte.com/)を利用しています
GeoJSONは[国土数値情報の道の駅のポイントデータ](https://nlftp.mlit.go.jp/ksj/gml/datalist/KsjTmplt-P35.html)を使っています
```Svelte
<script lang="ts">
  import "./app.css";
  import maplibregl from "maplibre-gl";
  import "maplibre-gl/dist/maplibre-gl.css";
  import { onMount } from "svelte";
  import { Label } from "$lib/components/ui/label/index.js";
  import * as RadioGroup from "$lib/components/ui/radio-group/index.js";

  const STYLES = [
    {
      name: "Voyager",
      url: "https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json",
    },
    {
      name: "Dark Matter",
      url: "https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json",
    },
  ];
  let map: maplibregl.Map;
  let styleUrl = $state(STYLES[0].url);

  $effect(() => {
    const setValue = styleUrl;
    if (!map) return;
    map.setStyle(setValue);
  });

  onMount(() => {
    map = new maplibregl.Map({
      container: "map",
      style: STYLES[0].url,
      center: [137, 36],
      zoom: 4,
    });
    map.on("load", async () => {
      map.addSource("roadside-station-source", {
        type: "geojson",
        data: "Roadside_Station.geojson",
      });
      map.addLayer({
        id: "roadside-station",
        type: "circle",
        source: "roadside-station-source",
        paint: {
          "circle-radius": 2,
          "circle-color": "#0040ff",
        },
      });
    });
  });
</script>

<main>
  <div id="map" class="h-[100vh]"></div>

  <div id="change-basemap" class="p-4 bg-white rounded-lg shadow-md absolute top-10 left-10 z-10">
    <RadioGroup.Root bind:value={styleUrl}>
      {#each STYLES as style}
        <div class="flex items-center space-x-2">
          <RadioGroup.Item value={style.url} id={style.name} />
          <Label class="cursor-pointer" for={style.name}>{style.name}</Label>
        </div>
      {/each}
    </RadioGroup.Root>
  </div>
</main>

<style>
</style>
```
Sveleteのりアクティビティな書き方によって、結構スッキリはしているのですが、このままの実装では背景スタイルを変更すると、重ねて表示しているレイヤーが消えてしまいます

## Svelte MapLibre GLなら大丈夫
Svelte Maplibre GLはそのあたりのケアがされており、背景地図を変えても上のレイヤーが消えることはありません

![Kapture 2024-12-21 at 10.46.24.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2788/174079d3-7b39-6a03-c373-fba3d462ce93.gif)

かつ、非常に見通しの良いソースコードになります
```Svelte
<script lang="ts">
  import "./app.css";
  import { MapLibre, GeoJSONSource, CircleLayer } from "svelte-maplibre-gl";
  import { Label } from "$lib/components/ui/label/index.js";
  import * as RadioGroup from "$lib/components/ui/radio-group/index.js";

  const STYLES = [
    {
      name: "Voyager",
      url: "https://basemaps.cartocdn.com/gl/voyager-gl-style/style.json",
    },
    {
      name: "Dark Matter",
      url: "https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json",
    },
  ];
  let styleUrl = $state(STYLES[0].url);
</script>

<main>
  <MapLibre
    class="h-[100vh]"
    style={styleUrl}
    zoom={4}
    maxPitch={80}
    center={{ lng: 137, lat: 36 }}
  >
    <GeoJSONSource data="Roadside_Station.geojson">
      <CircleLayer paint={{ "circle-radius": 2, "circle-color": "#0040ff" }} />
    </GeoJSONSource>
  </MapLibre>

  <div id="change-basemap" class="p-4 bg-white rounded-lg shadow-md absolute top-10 left-10 z-10">
    <RadioGroup.Root bind:value={styleUrl}>
      {#each STYLES as style}
        <div class="flex items-center space-x-2">
          <RadioGroup.Item value={style.url} id={style.name} />
          <Label class="cursor-pointer" for={style.name}>{style.name}</Label>
        </div>
      {/each}
    </RadioGroup.Root>
  </div>
</main>

<style>
</style>
```
どうでしょう、大変わかりやすい構成になっていると思いませんか？
載せるデータの変更や、追加、ポイントの色の変更や大きさなども、リアクティブに管理してしまえば容易にカスタマイズできるようになります
Svelte5で登場したRuneというシステムにとても相性が良いなと思いました

Reactだと似たようなライブラリーに[react-map-gl](https://github.com/visgl/react-map-gl)がありますが、使い勝手の点から見てもSvelteの方が使いやすいです
（この点に関しては私がReactにそれほど慣れていないという原因があるかもしれませんが…）

## まとめ
- Svelte MapLibre GLがとても便利なので、開発体験がとても良い
- Svelte MapLibre GLを使うために、フロントエンドのライブラリーにSvelteを選択するというのもおすすめ

## 少し宣伝
2024/02/14〜02/15に開催されるFOSS4G Hokkaido 2024において「SvelteKitとMaplibreGLJSでつくるWebGIS」というハンズオンを実施予定です
今回紹介したSvelte MapLibre GLをもう少し深く体験できるハンズオンにする予定です

ハンズオンには参加しなくても素敵なカンファレンスになると思いますので、ぜひ足を運んでいただければ幸いです

https://foss4g.hokkaido.jp/2024/