Vue.js

【Vue3】 leafletを使用してマップアプリを作成しよう

最近なんとなく、私の故郷である宮崎県の観光サイトでも作ってみようと思い、Vue3とleafletを使用して作成しました。
その際、vue3とleafletに関してのドキュメントが少なかったので、調べついでにこちらに記したいと思います。

ちなみに作成したサイトがこちら

leafletをプロジェクトに追加

始めに、Leafletは地図データを扱うためのJavaScript ライブラリです。
Vueで使用するには、下記コマンドでプロジェクトにパッケージを追加します。

npm install @vue-leaflet/vue-leaflet
こちらもチェック!

leafletの使い方

地図を表示してみる

導入できたら、ちゃっちゃと表示してみます。
今回はApp.vueに記載しています。

<script setup lang="ts">
  //leafletのcssとコンポーネントをインポート
  import "leaflet/dist/leaflet.css";
  import { LMap, LTileLayer, LMarker } from '@vue-leaflet/vue-leaflet';
</script>

<template>
  //インポートしたコンポーネントを使用する
  <LMap id="map" :zoom="8" :center="[32.205869963387336, 131.336749005514]" :use-global-leaflet="false">
    <LTileLayer url="https://cyberjapandata.gsi.go.jp/xyz/seamlessphoto/{z}/{x}/{y}.jpg" ></LTileLayer>
    <LMarker :lat-lng="[31.9109129,131.4213227]"></LMarker>  
  </LMap>
</template>

<style>
  //高さと幅指定する(今回は全画面表示)
  #app {
    width: 100vw;
    height: 100vh;
  }
</style>

上記コードで地図が表示されるはずです。
ポイントを一つずつ確認します。

scriptブロック

  import "leaflet/dist/leaflet.css";
  import { LMap, LTileLayer, LMarker } from '@vue-leaflet/vue-leaflet';

leaflet.cssとコンポーネントをインポートします。
コンポーネントは様々ありますので、下記公式サイトより必要な分をインポートします。
vue2用で英語サイトですが、ブラウザの翻訳機能を使えば普通に見れますので、こちらご参考ください。
最低限 「LMap, LTileLayer, LMarker」は必要かと思います。

■vue2-leafletサイト
https://vue2-leaflet.netlify.app/components/

templateブロック

  <LMap id="map" :zoom="8" :center="[32.205869963387336, 131.336749005514]" :use-global-leaflet="false">
    <LTileLayer url="https://cyberjapandata.gsi.go.jp/xyz/seamlessphoto/{z}/{x}/{y}.jpg" ></LTileLayer>
    <LMarker :lat-lng="[31.9109129,131.4213227]"></LMarker>  
  </LMap>
LMap

「LMap」が基本のコンポーネントで、他の全てのコンポーネントはLMapの中に入ります。
例で指定している属性は以下の通りです。

zoom初期表示のマップの拡大率
center初期表示のマップの中心
例では宮崎の中心地を指定しています。
use-global-leafletvue3用leafletはまだ出来たばかりです。
そのため、「use-global-leaflet="false"」を指定しないと地図が表示されません。今後改善されるかもしれません。
LTileLayer

マップサーバーからタイルをロードして表示するコンポーネント。
読み込むタイルは、OpenStreetMap や国土地理院など様残なオープンデータがありますが、今回は国土地理院の全国最新写真(シームレス)を使用しています。

url読み込むタイルのURL
サンプルは国土地理院のタイルを読み込んでいます

■国土地理院
https://maps.gsi.go.jp/development/ichiran.html

LMarker

マップ上に表示するマーカー(ピン)です。
複数指定可能。

lat-lngマーカーを配置する緯度と経度を指定します。

ちなみに緯度と経度はGoogleMapで検索した際にURLに表示されるので、そこから取得するのが簡単です。

styleブロック

地図を表示する大きさを指定しています。
今回は全画面表示にしています。

マーカー(ピン)を複数配置する

LMarkerの項目でも記載しましたが、マーカーは複数設置可能です。

  <LMap id="map" :zoom="8" :center="[32.205869963387336, 131.336749005514]" :use-global-leaflet="false">
    <LTileLayer url="https://cyberjapandata.gsi.go.jp/xyz/seamlessphoto/{z}/{x}/{y}.jpg" ></LTileLayer>
    <LMarker :lat-lng="[31.9109129,131.4213227]"></LMarker>
    <LMarker :lat-lng="[31.8892451,131.4414053]"></LMarker>
  </LMap>
vue-leafletマーカー複数配置

ただ、実際はDBなどからデータを読み込んで、ピンを設置することが多いでしょう。
その場合は、v-forを使用して複数配置します。

<script setup lang="ts">
  import "leaflet/dist/leaflet.css";
  import { LMap, LTileLayer, LMarker } from '@vue-leaflet/vue-leaflet';
  import {reactive} from "vue";

  // 配列に位置情報追加
  const markerList = new Map<number,{lat:number,lng:number}>();
  markerList.set(1,{lat:31.9109129,lng:131.4213227});
  markerList.set(2,{lat:31.8892451,lng:131.4414053});
  const marker = reactive(markerList);

</script>

<template>
  <LMap id="map" :zoom="8" :center="[32.205869963387336, 131.336749005514]" :use-global-leaflet="false">
    <LTileLayer url="https://cyberjapandata.gsi.go.jp/xyz/seamlessphoto/{z}/{x}/{y}.jpg" ></LTileLayer>
    
    <!-- v-forでLMarker複数追加 -->
    <LMarker 
      v-for="[id,markerItem] in marker"
      v-bind:key="id"
      :lat-lng="[markerItem.lat,markerItem.lng]">
    </LMarker>
  </LMap>
</template>

マーカー(ピン)の色を変更する

マーカーの色は、CSSを使用して変更することが可能です。

<script setup lang="ts">
  import "leaflet/dist/leaflet.css";

  // LIconコンポーネントの読込
  import { LMap, LTileLayer, LMarker ,LIcon } from '@vue-leaflet/vue-leaflet';
  import {reactive,ref} from "vue";

  // 配列にクラス名として使用するカテゴリ(cat)の追加
  const markerList = new Map<number,{lat:number,lng:number,cat:number}>();
  markerList.set(1,{lat:31.9109129,lng:131.4213227,cat:1});
  markerList.set(2,{lat:31.8892451,lng:131.4414053,cat:2});
  const marker = reactive(markerList);

</script>

<template>
  <LMap id="map" :zoom="8" :center="[32.205869963387336, 131.336749005514]" :use-global-leaflet="false">
    <LTileLayer url="https://cyberjapandata.gsi.go.jp/xyz/seamlessphoto/{z}/{x}/{y}.jpg" ></LTileLayer>
    <LMarker 
      v-for="[id,markerItem] in marker"
      v-bind:key="id"
      :lat-lng="[markerItem.lat,markerItem.lng]">
        <!-- LIcon に classNameでクラスを付与する -->
        <LIcon iconUrl="src/assets/marker-icon.png" :className="`cat${ markerItem.cat }`"></LIcon>
    </LMarker>
  </LMap>
</template>

<style>
  #app {
    width: 100vw;
    height: 100vh;
  }

  /* それぞれ付けたクラスにfilterプロパティを使用して色を変える */
  .cat1 {
    filter: hue-rotate(-45deg);
  }
  .cat2 {
    filter: hue-rotate(190deg);
  }
  .cat3 {
    filter: hue-rotate(120deg);
  }
</style>	
vue-leafletマーカー色変更

LIcnコンポーネントを使用するので、importします。
LIcnコンポーネントはマーカーのアイコンを指定するコンポーネントです。

  // LIconコンポーネントの読込
  import { LMap, LTileLayer, LMarker ,LIcon } from '@vue-leaflet/vue-leaflet';

デフォルトのアイコン画像を読み込み、クラスを付与します。

<!-- LIcon に classNameでクラスを付与する -->
<LIcon iconUrl="src/assets/marker-icon.png" :className="`cat${ markerItem.cat }`"></LIcon>
iconUrl読み込むアイコン画像のURLを指定します。
classNameアイコンに付与するクラス名を指定できます。
今回はそれぞれのアイコンの色を変えるために、cat1~cat3のクラス名を付与しています。

付与したそれぞれのクラスに「filter」プロパティを使用して色相を変更しました。
「filter」の使い方については、今回は割愛します。

/* それぞれ付けたクラスにfilterプロパティを使用して色を変える */
.cat1 {
  filter: hue-rotate(-45deg);
}
.cat2 {
  filter: hue-rotate(190deg);
}
.cat3 {
  filter: hue-rotate(120deg);
}

マーカー(ピン)の画像を変更する

先ほど使用したLIconコンポーネントを使用してマーカーの画像を変更することができます。
基本的には前項と同じですが、「iconSize」でアイコンのサイズを指定しておきます。

iconSizeアイコンのサイズを指定する。
<template>
  <LMap id="map" :zoom="8" :center="[32.205869963387336, 131.336749005514]" :use-global-leaflet="false">
    <LTileLayer url="https://cyberjapandata.gsi.go.jp/xyz/seamlessphoto/{z}/{x}/{y}.jpg" ></LTileLayer>
    <LMarker 
      v-for="[id,markerItem] in marker"
      v-bind:key="id"
      :lat-lng="[markerItem.lat,markerItem.lng]">

        <!-- LIcon に表示したい画像のURLを指定 -->
        <LIcon iconUrl="src/assets/marker-stop.png" :iconSize="[30, 30]"></LIcon>

    </LMarker>
  </LMap>
</template>

マーカー(ピン)クリックでポップアップ

アイコンをクリックした時のポップアップを表示できます。

<script setup lang="ts">
  import "leaflet/dist/leaflet.css";

  // LPopup コンポ―ネント読込
  import { LMap, LTileLayer, LMarker , LPopup } from '@vue-leaflet/vue-leaflet';
  import {reactive,ref} from "vue";

  // ポップアップに表示する文言の追加
  const markerList = new Map<number,{lat:number,lng:number,message:string}>();
  markerList.set(1,{lat:31.9109129,lng:131.4213227,message:"宮崎空港"});
  markerList.set(2,{lat:31.8892451,lng:131.4414053,message:"宮崎市役所"});
  markerList.set(3,{lat:31.9157451,lng:131.4318321,message:"宮崎駅"});
  const marker = reactive(markerList);

</script>

<template>
  <LMap id="map" :zoom="8" :center="[32.205869963387336, 131.336749005514]" :use-global-leaflet="false">
    <LTileLayer url="https://cyberjapandata.gsi.go.jp/xyz/seamlessphoto/{z}/{x}/{y}.jpg" ></LTileLayer>
    <LMarker 
      v-for="[id,markerItem] in marker"
      v-bind:key="id"
      :lat-lng="[markerItem.lat,markerItem.lng]">
        <!-- LPopupに表示するメッセージの追加 -->
        <LPopup>{{ markerItem.message }}</LPopup>
    </LMarker>
  </LMap>
</template>

マーカー(ピン)にイベント追加

マーカーにはイベントを設定することもできます。
今回は、クリック時に写真を別枠で表示するイベントを設定しましたが、自由にイベントやスタイルなどは設定してもらえたらと思います。

<script setup lang="ts">
  import "leaflet/dist/leaflet.css";
  import { LMap, LTileLayer, LMarker } from '@vue-leaflet/vue-leaflet';
  import {reactive,ref} from "vue";

  // 画像パスの項目を追加
  const markerList = new Map<number,{lat:number,lng:number,img:string}>();
  markerList.set(1,{lat:31.9109129,lng:131.4213227,img:"src/assets/miyazakistation_01.jpg"});
  markerList.set(2,{lat:31.8892451,lng:131.4414053,img:"src/assets/miyazakiairport_01.jpg"});
  markerList.set(3,{lat:31.9157451,lng:131.4318321,img:"src/assets/miyazaki_city_hall_01.jpg"});
  const marker = reactive(markerList);

 // クリックイベントとリアクティブ変数の設定
  const isOpen = ref(false);
  const img_url = ref("");
  const modal = (img:string):void => {
    isOpen.value = true;
    img_url.value = img
  }
</script>

<template>
  <LMap id="map" :zoom="8" :center="[32.205869963387336, 131.336749005514]" :use-global-leaflet="false">
    <LTileLayer url="https://cyberjapandata.gsi.go.jp/xyz/seamlessphoto/{z}/{x}/{y}.jpg" ></LTileLayer>
    <LMarker 
      v-for="[id,markerItem] in marker"
      v-bind:key="id"
      :lat-lng="[markerItem.lat,markerItem.lng]"
      @click="modal(markerItem.img)" <!-- クリックイベントの追加(引数に画像へのパスを渡しています) -->
      >
    </LMarker>
  </LMap>
 
  <!-- クリックされたら画像を開く -->	
  <img :src="img_url" :class="{ open: isOpen }" alt="">
</template>

<style>
  #app {
    width: 100vw;
    height: 100vh;
  }

  /* 開いた画像のスタイル */
  .open {
    position: absolute;
    top: 50px;
    right: 50px;
    z-index: 1000;
  }
</style>	

地図を表示する範囲を指定する

地図で何かアプリを作成しても、全然関係ないアフリカの地図などを見られても困りますよね。
そんな時は、地図の見える範囲を指定することができます。
ユーザーが範囲外に出ようとすると自動で、元の位置に戻らせることが可能です。

<template>
  <!-- maxBounds で表示範囲を設定 -->
  <LMap id="map" :zoom="8" :maxBounds="[[33.640355228896496, 129.8724412729661],[30.44643797225929, 132.32249668141318]]" :center="[32.205869963387336, 131.336749005514]" :use-global-leaflet="false">
    <LTileLayer url="https://cyberjapandata.gsi.go.jp/xyz/seamlessphoto/{z}/{x}/{y}.jpg" ></LTileLayer>
    <LMarker 
      v-for="[id,markerItem] in marker"
      v-bind:key="id"
      :lat-lng="[markerItem.lat,markerItem.lng]">
    </LMarker>
  </LMap>
</template>

範囲を設定したい位置の、左上と右下の緯度経度を指定しまs。
サンプルでは、九州だけ表示するように指定しています。
イメージを図で表しています。

まとめ

ここで記したこと事は、比較的よく使用するであろうものだけ記しており、leafletのごく一部の機能や、コンポーネントです。

他のコンポーネントは公式サイトから、使い方を見て頂けたらと思います。
leafletを使用すれば簡単に地図アプリが作成できるので、どのような物を作るか想像が膨らみますね。

■vue2-leafletサイト
https://vue2-leaflet.netlify.app/compo

-Vue.js
-, , ,