## 経緯
既存のライブラリを津空けば[[📜2021-10-16 Vue3でGoogleMapを使用]]することはできる。...が込み入ったことをやろうとすると自作した方がメンテナンス性がいい。
## やり方
インストールする。
```console
npm i -D @types/google.maps
npm i @googlemaps/js-api-loader
```
## 例
作り込んだ例。
- StreetViewと一緒に連動させている
- ドメインモデルのコードは記載しない
- 外界とのやりとり用なので支障ないはず
- サイズは適当
- 外部から指定可能にしたほうがいい
```ts
<script setup lang="ts">
import { onBeforeUnmount, onMounted, reactive, ref, watch } from "vue";
import _ from "lodash";
import LatLngLiteral = google.maps.LatLngLiteral;
import { Loader } from "@googlemaps/js-api-loader";
import { Coordinate } from "../domain/vo/Coordinate";
import { Latitude } from "../domain/vo/Latitude";
import { Longitude } from "../domain/vo/Longitude";
import { Spot } from "../domain/vo/Spot";
const API_KEY = "ひみつ";
const toGoogle = (c: Coordinate): LatLngLiteral => ({
lat: c.latDegree,
lng: c.lonDegree,
});
const props = defineProps<{
zoom: number;
center: Coordinate;
spots: Spot[];
}>();
interface State {
gmap: google.maps.Map | null;
smap: google.maps.StreetViewPanorama | null;
zoom: number;
center: LatLngLiteral;
markers: google.maps.Marker[];
centerMarker: google.maps.Marker | null;
autoZoom: boolean;
autoScroll: boolean;
}
const mapRef = ref(null);
const streetViewRef = ref(null);
const state = reactive<State>({
gmap: null,
smap: null,
zoom: props.zoom,
center: toGoogle(props.center),
markers: [],
centerMarker: null,
autoZoom: true,
autoScroll: true,
});
onMounted(async () => {
await new Loader({
apiKey: API_KEY,
version: "weekly",
}).load();
state.gmap = new google.maps.Map(mapRef.value!, {
center: state.center,
zoom: state.zoom,
scrollwheel: true,
});
state.smap = new google.maps.StreetViewPanorama(streetViewRef.value!, {
position: state.center,
scrollwheel: true,
});
state.gmap.setStreetView(state.smap);
state.gmap.addListener("center_changed", onChangeCenter);
state.centerMarker = new google.maps.Marker({
position: state.center,
icon: {
url: "/plus.png",
anchor: {
x: 8,
y: 8,
},
},
map: state.gmap,
});
});
onBeforeUnmount(async () => {
state.gmap?.unbindAll();
});
const removeAllMarkers = () => {
state.markers.forEach((x) => {
x.setVisible(false);
x.setMap(null);
});
};
watch(
() => props.center,
(center) => {
state.center = toGoogle(center);
state.centerMarker?.setPosition(state.center);
}
);
watch(
() => props.zoom,
(zoom) => {
state.zoom = zoom;
}
);
watch(
() => props.spots,
(spots) => {
removeAllMarkers();
state.markers = spots.map(
(x, i) =>
new google.maps.Marker({
position: toGoogle(x.coord),
label: {
text: x.label,
color: "white",
className: "google-map-marker",
fontSize: "1em",
},
map: state.gmap,
})
);
const bounds = new google.maps.LatLngBounds();
spots.forEach((x) => bounds.extend(toGoogle(x.coord)));
if (state.autoScroll) {
state.gmap?.setCenter(bounds.getCenter());
}
if (state.autoZoom) {
state.gmap?.fitBounds(bounds);
}
}
);
const emit = defineEmits<{
(e: "changeCenter", center: Coordinate): void;
}>();
const onChangeCenter = () => {
const ce = state.gmap?.getCenter();
if (!ce) {
return;
}
emit(
"changeCenter",
Coordinate.of(
{
lat: Latitude.of(ce.lat())._ok!,
lon: Longitude.of(ce.lng())._ok!,
},
"wgs84"
)
);
};
</script>
<template>
<div
ref="mapRef"
style="height: calc(50vh - 100px); width: calc(100vw - 600px)"
></div>
<div
ref="streetViewRef"
style="height: calc(50vh - 100px); width: calc(100vw - 600px)"
></div>
<div style="margin-top: 15px; display: flex; gap: 30px; justify-content: end">
<div>
<b style="padding: 0 5px">オートズーム</b>
<n-switch v-model:value="state.autoZoom">
<template #checked>ON</template>
<template #unchecked>OFF</template>
</n-switch>
</div>
<div>
<b style="padding: 0 5px">オートスクロール</b>
<n-switch v-model:value="state.autoScroll">
<template #checked>ON</template>
<template #unchecked>OFF</template>
</n-switch>
</div>
</div>
</template>
<style>
.google-map-marker {
padding: 5px;
background-color: #ff4500aa;
}
</style>
```
## 参考
- [TypeScriptでGoogle APIsを使おう \| Rhyztech blog](https://blog.rhyztech.net/googlemaps_with_typescript/)