🗺️

Sveltekit+MicroCMSの投稿にオリジナルの地図をつけてみる

に公開

MIERUNE(弊社)が開発した「Svelte Maplibre GL」というライブラリを使って、MicroCMS上のデータを地図に表示してみます。
このライブラリは、MapLibre GL JS を Svelte の世界で快適に使うためのライブラリです。

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

こちらのサイトに実装するために作成しました(まだ地図機能の公開はしてません)。

https://baseball-mascot.com

MapLibreとは

MapLibreとは、モダンなWeb地図にまつわるOSSを傘下に収める開発者コミュニティです。特に「MapLibre GL JS」は、ウェブ上で地図ビューアを作成するのに使われるライブラリで、複雑なデータでもスムーズに動作することが特徴です。

完成イメージ

地図のイメージ

地点の記事(投稿)に地図が追加されるようにしてみます。
地点にマーカーを置き、中心に表示させます。

ポップアップのイメージ

マーカーをクリックするとポップアップが表示されて、各地点の記事に飛べるようになってます。

想定するページの場所

各記事の[id]ディレクトリ内の+page.svelteで地図を表示する想定です。

地点データの繋ぎこみ

各地点が投稿として保存されています。
投稿には経度緯度を示す、「lat」および「lng」が保存されています。

    {
        "name": "きたぎんボールパーク",
        "lat": 39.650296,
        "lng": 141.137466
    },

microcmsの導入はこちらを参考にして、以下のように書いています

https://blog.microcms.io/sveltekit-tutorial/

src/lib/microcms.ts
...

export type Spot = {
  id: string;
  name: string;
  lat: number;
  lng: number;
  adress: string;
  url: string;
}
...

export const getSpotDetail = async (
  contentId: string,
  queries?: MicroCMSQueries
) => {
  return await client.getListDetail<Spot>({
    endpoint: "spot",
    contentId,
    queries,
  });
};
src/routes/spot/[id]/+page.server.ts
import { getSpotDetail} from '$lib/microcms';
import type { PageServerLoad } from './$types';

export const load: PageServerLoad = async ({ params }) => {
  const { id } = params;

  // IDに基づいてデータを取得
  const content = await getSpotDetail(id);

  return {
    content
  };
}

地図の実装

Map.svelte上で地図を作成します。
簡単な構成であれば、Svelte MapLibre GLのExamplesをそのまま使えると思います。

https://svelte-maplibre-gl.mierune.dev/

今回は、Examplesのうち、「Marker and Popup」をほとんどそのまま活用しています。
lat,lngを用いてマーカーを作成し、地図の中心をlat,lngにしています。
ポップアップでは、nameを表示した上で、リンクとしてidから個別ページを呼び出すようにしています。

https://svelte-maplibre-gl.mierune.dev/examples/marker-popup

src/lib/components/Map.svelte
<script lang="ts">
  import {
    MapLibre,
    Marker,
    Popup,
    FullScreenControl,
    NavigationControl,
    ScaleControl,
  } from "svelte-maplibre-gl";

  export let mapinit: {
    lat: number;
    lng: number;
    name: string;
    id: string;
  };

  let lnglat = { lng: mapinit.lng, lat: mapinit.lat };
</script>

<section>
  <MapLibre
    class="h-[45vh] w-full mx-auto"
    style="/path/to/style.json"
    zoom={15}
    center={{ lng: mapinit.lng, lat: mapinit.lat }}
  >
    <Marker bind:lnglat>
      <Popup anchor="bottom" offset={[0, -30]}>
        <!-- Popupの中身をタイトルとリンクに -->
        <span class="text-lg text-center">
          <a href="/spot/{mapinit.id}">
            {mapinit.name}
          </a>
        </span>
      </Popup>
    </Marker>
    <FullScreenControl position="top-left" />
    <NavigationControl />
    <ScaleControl />
  </MapLibre>
</section>


+page.svelteにコンポーネントを差し込みます。

src/routes/spot/[id]/+page.svelte
<script lang="ts">
  import type { PageData } from "./$types";
  export let data: PageData;
  import Map from "$lib/components/Map.svelte";
</script>

<section>
    <Map
      mapinit={{
        lat: data.lat,
        lng: data.lng,
        name: data.name,
        id: data.id,
      }}
    />
</section>

以上で実装が可能です。
今回は、マーカー1つの場合ですが、複数の場合もこのように対応が可能です。

<MapLibre
  class="h-[70vh] w-[80%] mx-auto"
  style="/path/to/style.json"
  zoom={3.5}
  center={{ lng: 137, lat: 37 }}
>
  <FullScreenControl position="top-left" />
  <NavigationControl />
  <ScaleControl />
  {#each data.contents as spot (spot.id)}
    {#if spot.lat !== undefined}
      <Marker lnglat={{ lng: spot.lng, lat: spot.lat }}>
        <Popup anchor="bottom" offset={[0, -30]}>
          <span class="text-lg">
            <a href="/spot/{spot.id}">
              {spot.name}
            </a>
          </span>
        </Popup>
      </Marker>
    {/if}
  {/each}
</MapLibre>
MIERUNEのZennブログ

Discussion