---
title: 自身のサイトにGoogle Mapを埋め込む
description: このページでは、Google Mapを自身のサイトに埋め込む方法を説明します。
---

import Tabs from '@theme/Tabs';
import TabItem from '@theme/TabItem';

このページでは、Google Mapを自身のサイトに埋め込み、フロントエンドから位置情報を更新する方法を説明します。

## 概要

Kuroco のコンテンツにはGoogle Mapの位置情報を保存するための拡張形式が準備されています。
Kuroco管理画面から位置情報を入力する場合は、Kuroco管理画面のコンテンツ編集画面で表示される地図上の位置をクリックするだけで位置情報を指定できますが、フロントエンドでマップを表示する場合や、指定した位置情報を保存するインターフェースを提供する場合にはAPIを利用した実装が必要になります。
このチュートリアルでは、フロントエンドにGoogle Mapsのインターフェースを表示し、位置情報を更新する方法を説明します。  

### 前提条件

:::info
このページはKurocoでのプロジェクトが構築済みであることを前提としています。  
まだ構築していない場合は、下記のチュートリアルを参照してください。  
[Kurocoビギナーズガイド](/ja/docs/tutorials/beginners-guide/)  
:::

:::info
本チュートリアルでは以下のバージョンでコードを書いています。  
Nuxt2: v2.15.8  
Nuxt3: v3.8.0  
:::

## GoogleMapの設定

### GCPで新しいプロジェクトを作成

まず、Google Cloud Platform（GCP）で新しいプロジェクトを作成します。
Google Cloud Platformにログインし、ヘッダー部のプロジェクトプルダウンをクリックしてください。  
表示されたポップアップ上部の「新しいプロジェクト」をクリックします。

![Image from Gyazo](https://t.gyazo.com/teams/diverta/1923da7ed0f335affd606e0357401173.png)

新しいプロジェクト設定画面で、任意のプロジェクト名を入力し、「作成」をクリックします。

![Image from Gyazo](https://t.gyazo.com/teams/diverta/97657b8d4ac9413a85dfef9a23a199a1.png)

以上で新しいプロジェクトが作成されました。

### APIの有効化

続いてGCPのコンソールで必要なAPIを有効にします。

まずGCPコンソールのヘッダー部のプルダウンから作成したプロジェクトを選択します。

![Image from Gyazo](https://t.gyazo.com/teams/diverta/9cc03d60984cfbdaad5527e7ec6c904d.png)

続いてサイドバーから「APIとサービス」- 「有効なAPIとサービス」を選択してください。

![Image from Gyazo](https://t.gyazo.com/teams/diverta/bd8e30b1dc7b41bddabe338fe57a35de.png)

サイドバーから「ライブラリ」を選択するとAPIライブラリページが表示されます。  

![Image from Gyazo](https://t.gyazo.com/teams/diverta/bbf06293f26e7d3b2c8bfab0e7311a71.png)

ここで「Places API」と「Maps JavaScript API」を検索しそれぞれAPIを有効にしてください。

![Image from Gyazo](https://t.gyazo.com/teams/diverta/5bc3b735d7049919eaca1f1040ed521b.png)

![Image from Gyazo](https://t.gyazo.com/teams/diverta/dcf6d1ffb5c71003e19504c7e6e2bf19.png)

次にAPIキーを発行します。サイドバーから「認証情報」を選択し、「+認証情報を作成」をクリックします。

![Image from Gyazo](https://t.gyazo.com/teams/diverta/d90fbf56e1abad84ff550a2ad636863c.png)

APIキーを選択します。

![Image from Gyazo](https://t.gyazo.com/teams/diverta/548d59ce70f66dce5962931aa84aa4bd.png)

これにより、APIキーが生成されます。後で使用しますので控えてください。

![Image from Gyazo](https://t.gyazo.com/teams/diverta/8167a435004c93af8cec6c377ce30aec.png)

### 許可ドメインの設定

ウェブサイトのドメインをAPIキー登録することで、APIの使用を制御します。これにより、他のウェブサイトからの不正アクセスが防げます。

認証情報の画面内にて、先ほど作成したAPIキーの右にあるボタンから「APIキーを編集」を選択します。

![Image from Gyazo](https://t.gyazo.com/teams/diverta/e03ad86d2bf670719ac2d4389d73bbb8.png)

APIキーに任意の名前を入力し、アプリケーションの制限の設定から「ウェブサイト」を選択します。

![Image from Gyazo](https://t.gyazo.com/teams/diverta/2181cc532512ffae3b6eeddc833594a6.png)

「ADD」ボタンをクリックし、利用する予定のサイトのドメインを入力、「完了」をクリックします。

![Image from Gyazo](https://t.gyazo.com/teams/diverta/a16c9bdc6ff9cd9c46c849301398be50.png)

## Kurocoの設定

### コンテンツ定義の設定

Kurocoのコンテンツ定義で拡張項目の任意の箇所に、以下の項目を設定します。

項目名 | 入力値
:-- | :--
項目名 | 地図（googleマップ）
識別子 | gmap
項目設定 | 地図


![Image from Gyazo](https://t.gyazo.com/teams/diverta/03d858234eab0adaccbc322ca5748b2c.png)

### エンドポイントの作成

Kurocoのコンテンツからマップの位置情報を取得するエンドポイントを作成します。  
以下のように入力してください。

項目名 | 入力値
:-- | :--
パス | `/rmcs-api/(API ID)/map/details/{topics_id}`
モデル | コンテンツ - Topics - details
APIリクエスト制限 | （閲覧を許可するグループまたはメンバーフィルタを選択）
キャッシュ | 86400
topics_group_id | （表示するコンテンツのコンテンツ定義ID）

![Image from Gyazo](https://t.gyazo.com/teams/diverta/15515a2a7ce1629825e3f94e4a670475.png)

次に、マップの位置情報更新用のエンドポイントを作成します。  
以下のように入力してください。

項目名 | 入力値
:-- | :--
パス | `/rmcs-api/(API ID)/map/update/{topics_id}`
モデル | コンテンツ - Topics - update
APIリクエスト制限 | （変更を許可するグループまたはメンバーフィルタを選択）
topics_group_id | （更新したいコンテンツのコンテンツ定義ID）
use_columns | `gmap`

![Image from Gyazo](https://t.gyazo.com/teams/diverta/1cf6f21eed5d3cb834e6c493fa277917.png)


## フロントエンドの実装

### モジュールのインストール  

Google MapsのVue.jsコンポーネントを提供するモジュールをインストールします。

<Tabs>
<TabItem value="Nuxt2" label="Nuxt2" default>

```sh
npm install vue2-google-maps
```

</TabItem>
<TabItem value="Nuxt3" label="Nuxt3" default>

```sh
npm install vue3-google-map
```

</TabItem>
</Tabs>

### GCP KEYをconfigに記載

生成されたAPIキーを、ウェブサイトの設定ファイル（通常はconfigファイル）に記述します。これにより、ウェブサイトがAPIを利用できるようになります。  
Nuxt.jsで実装している場合、.env などに以下のように記載してください。


```markup title=".env"
BASE_URL=https://sample-service-site.g.kuroco.app
GCP_KEY=**************************************
```

:::tip
.envファイルの内容をデプロイ時に利用する場合は、.gitignoreから.envを削除して、.envもGitHubで管理するか、
GitHubのシークレットに登録した内容をGitHubActionsで読み込んで使用します。
:::

上記の設定を読み込むため、設定ファイルに以下のように追記してください。

<Tabs>
<TabItem value="Nuxt2" label="Nuxt2" default>

```js title="nuxt.config.js"
export default {
  env: {
    GCP_KEY: process.env.GCP_KEY
  },
  // (中略)

  plugins: [
    // (中略)
    '@/plugins/vue2-google-maps.client'
  ],
```

</TabItem>
<TabItem value="Nuxt3" label="Nuxt3" default>


```ts title="nuxt.config.ts"
export default defineNuxtConfig({
    runtimeConfig: {
        // Public keys that are exposed to the client
        public: {
            gcpKey: process.env.GCP_KEY,
            apiBase: 'https://*********.g.kuroco.app'
        }
    },
```

</TabItem>
</Tabs>


#### (Nuxt2のみ)

plugins/ ディレクトリに`vue2-google-maps.client.js` というファイルを追加し、以下のように記載してください。

<Tabs>
<TabItem value="Nuxt2" label="Nuxt2" default>

```markup title="/plugins/vue2-google-maps.client.js"
import Vue from 'vue';
import * as VueGoogleMaps from 'vue2-google-maps';

Vue.use(VueGoogleMaps, {
    load: {
        key: process.env.GCP_KEY,
        libraries: 'places'
    }
});
```

</TabItem>
</Tabs>

### GoogleMapを表示するページの実装

ウェブサイトにGoogle Mapを表示してみましょう。
インストールしたコンポーネントを使用し、以下のように実装します。

<Tabs>
<TabItem value="Nuxt2" label="Nuxt2" default>

```markup title="/pages/map/details/_id.vue"
<template>
  <div class="container">
    <h3>地図（googleマップ）</h3>
    <form id="topics_edit" @submit.prevent="update">
      <div>
        <GmapMap
          ref="gmap"
          :center="mapCenter"
          :zoom="gmap_zoom"
          :map-type-id="gmap_type"
          style="width: 500px; height: 300px"
        >
          <GmapMarker
            v-if="markPlace"
            :position="markPlace"
          />
        </GmapMap>
      </div>
    </form>
  </div>
</template>

<script>
export default {
  async asyncData({ $axios, route }) {
    const id = route.params.id;
    const url = `/rcms-api/1/test/${id}`;

    const contents = await $axios
      .$get(url)
      .then((response) => {
        if (response.details) {
          return response.details;
        }
        return {};
      })
      .catch((error) => {
        console.log(error);
        return {};
      });
    // Googleマップの初期状態をセット
    let mapCenter = { lat: 35.66107078220203, lng: 139.7584319114685 };
    let markPlace = null;
    if (contents.gmap?.gmap_x && contents.gmap?.gmap_y) {
      const lat = Number(contents.gmap.gmap_y);
      const lng = Number(contents.gmap.gmap_x);
      mapCenter = { lat, lng };
      markPlace = { lat, lng };
    }

    return {
      mapCenter,
      markPlace,
      id,
      contents,
      errors: []
    };
  },
  computed: {
    gmap_zoom() {
      return Number(this.contents.gmap?.gmap_zoom) || 15;
    },
    gmap_type() {
      return this.contents.gmap?.gmap_type || 'roadmap';
    }
  }
};
</script>
```

</TabItem>
<TabItem value="Nuxt3" label="Nuxt3" default>

```markup title="/pages/map/details/[id].vue"
<template>
  <div v-if="data" class="container">
    <h3>地図（googleマップ）</h3>
    <form id="topics_edit" @submit.prevent="update">
      <div>
        <GoogleMap
          :mapId="MAP_ID"
          ref="gmap"
          :center="mapCenter"
          :zoom="gmap_zoom"
          :map-type-id="gmap_type"
          style="width: 500px; height: 300px"
          @click="mark($event)"
          @zoom_changed="setZoom"
          @maptypeid_changed="gmap_type = $event"
          :api-key="key"
        >
          <AdvancedMarker
            v-if="markPlace"
            :options="markerOptions"
            @click="mapClicked"
          />
        </GoogleMap>
      </div>
      <input type="submit" value="Save" />
    </form>
  </div>
</template>

<script setup>
import { AdvancedMarker, GoogleMap } from "vue3-google-map";
const route = useRoute();
const config = useRuntimeConfig();
const key = config.public.gcpKey;
const gmap = ref(null);
const mapCenter = ref({ lat: 35.66107078220203, lng: 139.7584319114685 });
const markPlace = ref(null);
const markerOptions = computed(() => ({
  position: markPlace.value || mapCenter.value,
  draggable: true,
}));
const id = ref(route.params.id);
const contents = ref({});
const MAP_ID = "DEMO_MAP_ID";
console.log("googleMap", gmap.value);
const { data } = await useAsyncData("mapDetails", async () => {
  const url = `/rcms-api/1/newsdetail/${id.value}`;
  try {
    const response = await $fetch(url, {
      method: "GET",
      baseURL: config.public.apiBase,
      credentials: "include",
    });
    if (response.details) {
      return response.details;
    }
    return {};
  } catch (error) {
    return {};
  }
});
onMounted(() => {
  contents.value = data.value;
  if (contents.value.gmap?.gmap_x && contents.value.gmap?.gmap_y) {
    const lat = Number(contents.value.gmap.gmap_y)
      ? Number(contents.value.gmap.gmap_y)
      : 35.66107078220203;
    const lng = Number(contents.value.gmap.gmap_x)
      ? Number(contents.value.gmap.gmap_x)
      : 139.7584319114685;
    mapCenter.value = { lat, lng };
    markPlace.value = { lat, lng };
  }
});
const gmap_zoom = computed({
  get: () => Number(contents.value.gmap?.gmap_zoom) || 15,
  set: (val) => {
    if (!contents.value.gmap) contents.value.gmap = {};
    contents.value.gmap.gmap_zoom = String(val);
  },
});
const gmap_type = computed({
  get: () => contents.value.gmap?.gmap_type || "roadmap",
  set: (val) => {
    if (!contents.value.gmap) contents.value.gmap = {};
    contents.value.gmap.gmap_type = val;
  },
});
function mapClicked(event) {
  // console.log("mapCLicked", { event });
}
function setZoom() {
  contents.value.gmap.gmap_zoom = gmap.value.zoom;
}
</script>
``` 

</TabItem>
</Tabs>

:::caution
利用するエンドポイントのURLはご自身のものに調整してください。
:::

実行結果は以下のようになります。  
![Image from Gyazo](https://t.gyazo.com/teams/diverta/78356a2a924751626fccc06779951e14.png)

### GoogleMapの位置情報を更新するページの実装

続いて、フロントエンドから位置情報を更新できるようにしてみましょう。

<Tabs>
<TabItem value="Nuxt2" label="Nuxt2" default>

```markup title="/pages/map/edit/_id.vue"
<template>
  <div class="container">
    <h3>地図（googleマップ）</h3>
    <div>
      地図上をクリックすると設定される位置が変わります。ズームなどの状態も設定できます。
    </div>
    <form id="topics_edit" @submit.prevent="update">
      <div>
        <form onsubmit="return false;">
          <GmapAutocomplete
            :options="{fields: ['geometry']}"
            :select-first-on-enter="true"
            @place_changed="setPlace"
          />
          <GmapMap
            ref="gmap"
            :center="mapCenter"
            :zoom="gmap_zoom"
            :map-type-id="gmap_type"
            style="width: 500px; height: 300px"
            @click="mark($event)"
            @zoom_changed="gmap_zoom = $event"
            @maptypeid_changed="gmap_type = $event"
          >
            <GmapMarker
              v-if="markPlace"
              :position="markPlace"
            />
          </GmapMap>
        </form>
      </div>
      <input
        type="submit"
        value="保存"
      >
    </form>
  </div>
</template>

<script>
export default {
  async asyncData({ $axios, route }) {
    const id = route.params.id;
    const url = `/rcms-api/1/test/${id}`;

    const contents = await $axios
      .$get(url)
      .then((response) => {
        if (response.details) {
          return response.details;
        }
        return {};
      })
      .catch((error) => {
        console.log(error);
        return {};
      });
    // Googleマップの初期状態をセット
    let mapCenter = { lat: 35.66107078220203, lng: 139.7584319114685 };
    let markPlace = null;
    if (contents.gmap?.gmap_x && contents.gmap?.gmap_y) {
      const lat = Number(contents.gmap.gmap_y);
      const lng = Number(contents.gmap.gmap_x);
      mapCenter = { lat, lng };
      markPlace = { lat, lng };
    }

    return {
      mapCenter,
      markPlace,
      id,
      contents,
      errors: []
    };
  },
  computed: {
    gmap_zoom: {
      get() { return Number(this.contents.gmap?.gmap_zoom) || 15; },
      set(val) { this.contents.gmap.gmap_zoom = String(val); }
    },
    gmap_type: {
      get() { return this.contents.gmap?.gmap_type || 'roadmap'; },
      set(val) { this.contents.gmap.gmap_type = val; }
    }
  },
  methods: {
    setPlace(place) {
      if (place.geometry) {
        this.markPlace = {
          lat: place.geometry.location.lat(),
          lng: place.geometry.location.lng()
        };
        if (place.geometry.viewport) {
          this.$refs.gmap.fitBounds(place.geometry.viewport);
        } else {
          this.$refs.gmap.panTo(place.geometry.location);
        }
      }
    },
    mark(event) {
      this.markPlace = {
        lat: event.latLng.lat(),
        lng: event.latLng.lng()
      };
    },
    async update() {
      const params = {
        gmap: {
          gmap_x: '',
          gmap_y: '',
          gmap_zoom: (this.contents?.gmap?.gmap_zoom || 15),
          gmap_type: (this.contents?.gmap?.gmap_type || 'roadmap')
        }
      };
      if (this.markPlace) {
        params.gmap.gmap_x = String(this.markPlace.lng);
        params.gmap.gmap_y = String(this.markPlace.lat);
      }
      await this.$axios.post(
        '/rcms-api/1/update_news/' + this.$route.params.id,
        params
      ).then((response) => {
        if (response.data.errors?.length) {
          console.log(response.data.errors);
        }
        this.errors = [];
      }).catch((error) => {
        console.log(error);
      });
    }
  }
};
</script>
```

</TabItem>
<TabItem value="Nuxt3" label="Nuxt3" default>

```markup title="/pages/map/edit/[id].vue"
<template>
  <div v-if="data" class="container">
    <h3>地図（googleマップ）</h3>
    <div>
      地図上をクリックすると設定される位置が変わります。ズームなどの状態も設定できます。
    </div>
    <form id="topics_edit" @submit.prevent="update">
      <div>
        <GoogleMap
          :mapId="MAP_ID"
          ref="gmap"
          :center="mapCenter"
          :zoom="gmap_zoom"
          :map-type-id="gmap_type"
          style="width: 500px; height: 300px"
          @click="mark($event)"
          @zoom_changed="setZoom"
          @maptypeid_changed="gmap_type = $event"
          :api-key="key"
        >
          <AdvancedMarker
            v-if="markPlace"
            :options="markerOptions"
            @click="mapClicked"
          />
        </GoogleMap>
      </div>
      <input type="submit" value="保存" />
    </form>
  </div>
</template>

<script setup>
import { AdvancedMarker, GoogleMap } from "vue3-google-map";
const route = useRoute();
const config = useRuntimeConfig();
const key = config.public.gcpKey;
const gmap = ref(null);
const mapCenter = ref({ lat: 35.66107078220203, lng: 139.7584319114685 });
const markPlace = ref(null);
const markerOptions = computed(() => ({
  position: markPlace.value || mapCenter.value,
  draggable: true,
}));
const id = ref(route.params.id);
const contents = ref({});
const errors = ref([]);
const MAP_ID = "DEMO_MAP_ID";
console.log("googleMap", gmap.value);
const { data } = await useAsyncData("mapDetails", async () => {
  const url = `/rcms-api/1/newsdetail/${id.value}`;
  try {
    const response = await $fetch(url, {
      method: "GET",
      baseURL: config.public.apiBase,
      credentials: "include",
    });
    if (response.details) {
      return response.details;
    }
    return {};
  } catch (error) {
    return {};
  }
});

onMounted(() => {
  contents.value = data.value;
  if (contents.value.gmap?.gmap_x && contents.value.gmap?.gmap_y) {
    const lat = Number(contents.value.gmap.gmap_y)
      ? Number(contents.value.gmap.gmap_y)
      : 35.66107078220203;
    const lng = Number(contents.value.gmap.gmap_x)
      ? Number(contents.value.gmap.gmap_x)
      : 139.7584319114685;
    mapCenter.value = { lat, lng };
    markPlace.value = { lat, lng };
  }
});

const gmap_zoom = computed({
  get: () => Number(contents.value.gmap?.gmap_zoom) || 15,
  set: (val) => {
    if (!contents.value.gmap) contents.value.gmap = {};
    contents.value.gmap.gmap_zoom = String(val);
  },
});
const gmap_type = computed({
  get: () => contents.value.gmap?.gmap_type || "roadmap",
  set: (val) => {
    if (!contents.value.gmap) contents.value.gmap = {};
    contents.value.gmap.gmap_type = val;
  },
});

function mark(event) {
  markPlace.value = {
    lat: event.latLng.lat(),
    lng: event.latLng.lng(),
  };
  update();
}

function setZoom() {
  contents.value.gmap.gmap_zoom = gmap.value.map.zoom;
}

async function update() {
  const params = {
    gmap: {
      gmap_x: "",
      gmap_y: "",
      gmap_zoom: String(contents.value?.gmap?.gmap_zoom) || "15",
      gmap_type: contents.value?.gmap?.gmap_type || "roadmap",
    },
  };
  if (markPlace.value) {
    params.gmap.gmap_x = String(markPlace.value.lng);
    params.gmap.gmap_y = String(markPlace.value.lat);
  }
  try {
    const response = await $fetch(
      "/rcms-api/1/update_news/" + route.params.id,
      {
        method: "POST",
        credentials: "include",
        baseURL: config.public.apiBase,
        body: params,
      }
    );
    // console.log(response);
    if (response.data.errors?.length) {
      console.log(response.data.errors);
    }
    errors.value = [];
  } catch (error) {}
}
</script>

```

</TabItem>
</Tabs>

:::caution
利用するエンドポイントのURLはご自身のものに調整してください。
:::

実行結果は以下のようになります。  
![Image from Gyazo](https://t.gyazo.com/teams/diverta/a3e5f55c3d08dfa97cf5799c7fe7eca7.png)

保存ボタンをクリックするとピンを立てた位置やズームの状態などがKurocoのDBに書き込まれます。

:::caution
上記のサンプルコードでは簡単のため、非ログイン状態でもページが表示されるようになってますが、通常はログインしてから更新をおこないます。ログインについては以下をご参照ください。  
[KurocoとNuxt.jsで、ログイン画面を構築する](/ja/docs/tutorials/integrate-login/)
:::

以上で、Google Mapをあなたのウェブサイトに埋め込み、ウェブサイト上からKuroco管理画面と同様に位置情報を変更する事ができるようになりました。これらの手順によって、ウェブサイトのユーザーに素晴らしい地図体験を提供できます。