最近なんとなく、私の故郷である宮崎県の観光サイトでも作ってみようと思い、Vue3とleafletを使用して作成しました。
その際、vue3とleafletに関してのドキュメントが少なかったので、調べついでにこちらに記したいと思います。
ちなみに作成したサイトがこちら
■宮崎探索マップ
https://map.web-create-kokusyo.com/
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-leaflet | vue3用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>
ただ、実際は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>
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