5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

MapLibreAdvent Calendar 2024

Day 21

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

Posted at

これは MapLibre Advent Calendar 2024 21日目の記事です。

はじめに

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

彗星のごとく登場したSvelte MapLibre GL

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

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

Maplibre GL JSでのちょっとした困りごと

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

Kapture 2024-12-21 at 10.44.51.gif

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

ソースコードは以下のような感じです(Svelte MapLibre GLのExampleを書き換えた感じになっています)
UIライブラリーはshadcn-svelteを利用しています
GeoJSONは国土数値情報の道の駅のポイントデータを使っています

<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

かつ、非常に見通しの良いソースコードになります

<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がありますが、使い勝手の点から見てもSvelteの方が使いやすいです
(この点に関しては私がReactにそれほど慣れていないという原因があるかもしれませんが…)

まとめ

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

少し宣伝

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

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

5
0
2

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
5
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?