vue3-google-mapを使って複数のアイコンをGoogle Map上に登録

### 概要

https://qiita.com/tthogho1/items/72110d63fee86617aae0


に対して機能追加


https://qiita.com/tthogho1/items/9b738c091230455cf3cb


で構築したデータベースに対して、GraphQLを使用してWebカメラの情報を取得し、vue3-google-mapを使用してGoogle Map上にマーカーを設定した。
個人的には、検索結果を変数に設定するのみで、Google Map上のマーカーが更新されるのが便利だと思った。

### マーカー登録部分
json配列webCamsに格納されているWebカメラの情報をGoogle Mapに登録している。

```vue
<template>
    <GoogleMap id="gmap" ref="mapRef" api-key="<googlemap api key>" style="width: 100%; height: 500px" :center="center" :zoom="15">
      <Marker :options="markerOptions" />
      <MarkerCluster>
        <Marker v-for="(webCam, i) in webCams" :options="{ position: {lat:webCam.location.latitude,lng:webCam.location.longitude} }" :key="i">
            <InfoWindow>
                <div><a v-bind:href="webCam.player.day.link" target="_blank">{{ webCam.title }}</a></div>
                <div><img :src="webCam.image.current.thumbnail" /></div>
            </InfoWindow>
        </Marker>
      </MarkerCluster>
    </GoogleMap>
</template>
```

複数のアイコンを登録するには、vue3-google-mapライブラリのMarkerClusterコンポーネントを使用する。
また、InfoWindowタグを設定することにより、各マーカーに対して情報Windowを設定することができる。Json配列webCamsの中身が変更されると、マーカーも入れ替わる。個々のJsonには、「タイトル」「WebカメラのPlayerのリンク」「サムネイルへのリンク」を持たせた、

```typescript
type  webCamObj = {
    id:string;
    status: string;
    title:  string;
    location : {
        latitude: number;
        longitude: number;
    }
    player : {
        day:{
            available:string;
            link:string;
        }
    } 
    image : {
            current : {
            thumbnail:string;
        }
    };
};
```
実際のイメージは以下のような感じ。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1284235/004133b0-135f-b8a5-1b9e-bca420973e45.png)



### 検索結果を配列に設定する部分
Google Mapの緯度、経度の範囲を指定して、GraphQLを使用してMongoDB AtlasからWebカメラ情報をMax200件を検索した。取得したデータを以下の箇所で配列webCamsに設定する。webCamsに設定された内容で、Google Map上のマーカーが更新される。Javascriptで自作する場合、マーカーの削除、登録で手間がかかる。
```typescript
webCams.value = Array.from(data.data.webcams);
```
getWebCamList関数は、Google Mapのイベントリスナーに登録し、Mapの範囲の変更とともにマーカーが入れ替わるようにした。
(以下のコードでは範囲指定が適当すぎて取れない箇所がある。)

```typescript
const webCams = ref<Array<webCamObj>>([]);    
const authToken = ref('');
const getWebCamList = function(map:any){
    const latlngBound = map.getBounds();
    const latlngNE = latlngBound.getNorthEast();
    const latlngSW = latlngBound.getSouthWest();    
    
    const latitude_gte = latlngSW.lat(), latitude_lt = latlngNE.lat(), longitude_gte = latlngSW.lng(), longitude_lt = latlngNE.lng();
    const token = 'Bearer ' + authToken.value ;
	const queryMsg = `query {
  		webcams(query:{status:"active",location:{
                        longitude_lt:${longitude_lt},
                        longitude_gte:${longitude_gte},
                        latitude_lt:${latitude_lt},
                        latitude_gte:${latitude_gte}}}
    	,limit:200
    	,sortBy:ID_ASC) {
			id
			title
    		location{
     		 	latitude
      			longitude
    		}
            player{
               day{
                 available
                 link
              }
           }
           image{
                current{
                    thumbnail
                }    
           }
  		}
	}`;

    const gmap  = document.getElementById("gmap");
    if (gmap != null && gmap != undefined){
            gmap.style.pointerEvents ="none";
    }
	fetch('https://realm.mongodb.com/api/client/v2.0/app/<アプリID>/graphql', {
  		method: 'POST',
  		headers: {
			'Authorization': token  ,
    		'Content-Type': 'application/json'
  		},
  		body: JSON.stringify({
      		query: queryMsg
  		})
	})
	.then(response => response.json())
	.then(data =>{
        webCams.value = Array.from(data.data.webcams);
        const gmap  = document.getElementById("gmap");
        if (gmap != null && gmap != undefined){
            gmap.style.pointerEvents = "auto";
        }
	})
	.catch(error => console.error(error));
};
```

### 所感
変数の更新とともに、Map上のマーカーの情報も更新されるところが便利に感じた。

##### index.vue全文
```vue
<template>
    <GoogleMap id="gmap" ref="mapRef" api-key="<googlemap api key>" style="width: 100%; height: 500px" :center="center" :zoom="15">
      <Marker :options="markerOptions" />
      <MarkerCluster>
        <Marker v-for="(webCam, i) in webCams" :options="{ position: {lat:webCam.location.latitude,lng:webCam.location.longitude} }" :key="i">
            <InfoWindow>
                <div><a v-bind:href="webCam.player.day.link" target="_blank">{{ webCam.title }}</a></div>
                <div><img :src="webCam.image.current.thumbnail" /></div>
            </InfoWindow>
        </Marker>
      </MarkerCluster>
    </GoogleMap>
</template>

<style>
#gmap {
    pointer-events: auto; 
}
</style>

<script setup lang="ts">
import { GoogleMap, InfoWindow, Marker ,MarkerCluster} from "vue3-google-map";
import { ref } from "vue";
import { webCamObj } from "./def/webCam"

const mapRef = ref(null);
const center = ref({ lat: 0, lng: 0 }); // first position
const markerOptions = ref({ position: center})

onMounted(() => {
    getAuthenticate();
});

const webCams = ref<Array<webCamObj>>([]);    
const authToken = ref('');
const getAuthenticate = function () {
	fetch('https://realm.mongodb.com/api/client/v2.0/app/<アプリID>/auth/providers/api-key/login', {
  		method: 'POST',
  		headers: {
    		'Content-Type': 'application/json'
  		},
  		body: JSON.stringify({
    		"key": "<MongoDB Atlas APIキー>"
 		})
	})
	.then(response => response.json())
	.then(data =>{
		authToken.value = data.access_token; 
	}).catch(error => console.error(error));
};

const getWebCamList = function(map:any){
    const latlngBound = map.getBounds();
    const latlngNE = latlngBound.getNorthEast();
    const latlngSW = latlngBound.getSouthWest();    
    
    const latitude_gte = latlngSW.lat(), latitude_lt = latlngNE.lat(), longitude_gte = latlngSW.lng(), longitude_lt = latlngNE.lng();
    const token = 'Bearer ' + authToken.value ;
	const queryMsg = `query {
  		webcams(query:{status:"active",location:{
                        longitude_lt:${longitude_lt},
                        longitude_gte:${longitude_gte},
                        latitude_lt:${latitude_lt},
                        latitude_gte:${latitude_gte}}}
    	,limit:200
    	,sortBy:ID_ASC) {
			id
			title
    		location{
     		 	latitude
      			longitude
    		}
            player{
               day{
                 available
                 link
              }
           }
           image{
                current{
                    thumbnail
                }    
           }
  		}
	}`;

    const gmap  = document.getElementById("gmap");
    if (gmap != null && gmap != undefined){
            gmap.style.pointerEvents ="none";
    }
	fetch('https://realm.mongodb.com/api/client/v2.0/app/<アプリID>/graphql', {
  		method: 'POST',
  		headers: {
			'Authorization': token  ,
    		'Content-Type': 'application/json'
  		},
  		body: JSON.stringify({
      		query: queryMsg
  		})
	})
	.then(response => response.json())
	.then(data =>{
        webCams.value = Array.from(data.data.webcams);
        const gmap  = document.getElementById("gmap");
        if (gmap != null && gmap != undefined){
            gmap.style.pointerEvents = "auto";
        }
	})
	.catch(error => console.error(error));
};
//
// getLocation is called when the map is ready
//
const getLocation = function(map:any,center:any,markerOptions:any){
    const markerIcon = {
        url:'./images/man.png',
        scaledSize: new mapRef.value.api.Size(30, 30)
    }
    navigator.geolocation.getCurrentPosition(
            // success callback
            function(position) {
                const lat = position.coords.latitude;
                const lng = position.coords.longitude;
                map.panTo({ lat, lng });
                center.value = { lat: lat, lng: lng };
                markerOptions.value = { position: center, icon: markerIcon };
            },
            // error callback
            function(error) {
                console.error(`Error getting location: ${error.message}`);
            }
    );
} 
watch(() => mapRef.value?.ready, (ready) => {
    if (!ready) return;
    const map = mapRef.value.map;
    getLocation(map,center,markerOptions);
    map.addListener("idle", (e:any) => {
        getWebCamList(map);
    });
})
</script>
```